openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,884 lines • 95.3 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { o as getPluginCache } from "./plugin-cache-BABafcXD.mjs";
import { l as normalizeSortedUniqueStringEntries } from "./string-normalization-CECOqf64.mjs";
import { o as resolveCompatibilityHostVersion } from "./version-BJGPJZu6.mjs";
import { n as isDeeplyFrozenPlainData } from "./immutable-data-MyNs7ITg.mjs";
import { i as readBundledDiscoveryModeMemoized } from "./bundled-discovery-state-C2EXzdGy.mjs";
import { n as isPathInside } from "./path-safety-6KJ2Rbnw.mjs";
import { f as readPluginCacheFile, l as pluginCacheRealpathSync, s as pluginCacheExistsSync } from "./package-manifest-CEbdS_C4.mjs";
import { t as isPluginEnabledByDefaultForPlatform } from "./default-enablement-CEIbpabL.mjs";
import { r as hasKind } from "./slots-DarSawuQ.mjs";
import { c as normalizePluginId, f as resolveEffectiveEnableState, p as resolveEffectivePluginActivationState, r as createPluginActivationSource, u as normalizePluginsConfig } from "./config-state-DY_WJYue.mjs";
import { t as hashJson } from "./installed-plugin-index-hash-N3AVaUCF.mjs";
import { D as resolvePluginCandidateInstallOwner, T as isPluginCandidateInstallOwnerAmbiguous, n as discoverOpenClawPlugins } from "./discovery-Dma3bVNk.mjs";
import { t as shouldRejectHardlinkedPluginFiles } from "./hardlink-policy-C9pW4xqI.mjs";
import { n as recordInstalledPluginIndexInstallOwner, r as resolveInstalledPluginIndexInstallOwner, t as isInstalledPluginIndexInstallOwnerAmbiguous } from "./installed-plugin-index-install-owner-CQJv99I6.mjs";
import { a as getPluginInstallRecordMapEntry, c as parsePluginInstallRecordMap, i as createPluginInstallRecordMap, u as setPluginInstallRecordMapEntry } from "./installed-plugin-record-match-CVFkDlud.mjs";
import { t as resolvePluginDoctorContractArtifact } from "./doctor-contract-artifact-xZyiClKv.mjs";
import { t as describePluginInstallSource } from "./install-source-info-fU5T9zEW.mjs";
import { a as resolvePluginManifestInstallOwner } from "./manifest-registry-build-CSMcsErL.mjs";
import { r as resolveInstalledPluginIndexStorePath } from "./installed-plugin-index-store-path-vGDuKHrq.mjs";
import { i as loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader-BP4r74yH.mjs";
import { t as loadPluginManifestRegistryCore } from "./manifest-registry-fNbgdLOV.mjs";
import path from "node:path";
//#region src/plugins/compat/agent-list-runtime-projection-records.ts
const AGENT_LIST_RUNTIME_PROJECTION_COMPAT_RECORDS = [{
code: "agent-list-runtime-projection",
status: "deprecated",
owner: "sdk",
introduced: "2026-07-27",
deprecated: "2026-10-02",
warningStarts: "2026-10-02",
removeAfter: "2027-01-02",
replacement: "Canonical `cfg.agents.entries`, or `listAgentIds` / `resolveAgentConfig` from `openclaw/plugin-sdk/agent-runtime`",
docsPath: "/plugins/sdk-migration/how-to-migrate#agent-roster-config",
surfaces: ["non-enumerable runtime OpenClawConfig.agents.list projection"],
diagnostics: ["plugin compatibility registry and migration documentation; no runtime warnings"],
tests: ["src/config/runtime-overrides.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "The runtime agents.list projection introduced in #113146 remains untyped compatibility for plugins built against stable SDK releases through 2026.9.x; authored configs use agents.entries without default markers."
}];
//#endregion
//#region src/plugins/compat/deprecation-marking.ts
const MARKING_DATE = "2026-07-25";
const DEPRECATION_MARKING = {
status: "removal-pending",
introduced: MARKING_DATE,
deprecated: MARKING_DATE,
warningStarts: MARKING_DATE,
removeAfter: "2026-10-01"
};
/** The elapsed window does not replace each family's required reader-migration proof. */
const DEPRECATION_MARKING_COMPAT_RECORDS = [
{
code: "plugin-sdk-channel-setup-input-fields",
...DEPRECATION_MARKING,
owner: "channel",
replacement: "plugin-local setup input intersections that declare each owning channel field; retain each field until a new published-plugin artifact sweep finds no reader",
docsPath: "/plugins/sdk-migration#published-channel-setup-compatibility",
surfaces: [
"ChannelSetupInput.privateKey",
"ChannelSetupInput.secret",
"ChannelSetupInput.botToken",
"ChannelSetupInput.appToken",
"ChannelSetupInput.signingSecret",
"ChannelSetupInput.mode",
"ChannelSetupInput.cliPath",
"ChannelSetupInput.authDir",
"ChannelSetupInput.httpUrl",
"ChannelSetupInput.httpPort",
"ChannelSetupInput.webhookPath",
"ChannelSetupInput.webhookUrl",
"ChannelSetupInput.userId",
"ChannelSetupInput.accessToken",
"ChannelSetupInput.password",
"ChannelSetupInput.deviceName",
"ChannelSetupInput.url",
"ChannelSetupInput.baseUrl",
"ChannelSetupInput.code",
"ChannelSetupInput.groupChannels",
"ChannelSetupInput.dmAllowlist",
"ChannelSetupInput.autoDiscoverChannels"
],
diagnostics: ["TypeScript @deprecated annotations on the reader-backed ChannelSetupInput compatibility tier", "published-plugin artifact reader sweep required before field removal"],
tests: ["src/plugin-sdk/channel-setup.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "ChannelSetupInput keeps its reader-backed channel fields through the dated migration window while plugins move them into plugin-local input types."
},
{
code: "plugin-sdk-broad-runtime-barrels",
...DEPRECATION_MARKING,
owner: "sdk",
replacement: "focused plugin SDK subpaths for each runtime capability; retain until bundled and published plugins no longer import any of the seven broad barrels",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"openclaw/plugin-sdk/agent-runtime",
"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.useCache",
"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.cacheOnly",
"openclaw/plugin-sdk/agent-runtime loadModelCatalog params.metadataSnapshot",
"openclaw/plugin-sdk/agent-runtime loadModelCatalog",
"openclaw/plugin-sdk/cli-runtime",
"openclaw/plugin-sdk/conversation-runtime",
"openclaw/plugin-sdk/hook-runtime",
"openclaw/plugin-sdk/media-runtime",
"openclaw/plugin-sdk/media-runtime buildAgentMediaPayload",
"openclaw/plugin-sdk/plugin-runtime",
"openclaw/plugin-sdk/security-runtime"
],
diagnostics: ["TypeScript @deprecated annotations on broad plugin SDK barrels", "plugin boundary report compatibility inventory"],
tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Broad agent, CLI, conversation, hook, media, plugin, and security runtime barrels remain available while bundled and external plugins migrate to focused subpaths."
},
{
code: "plugin-sdk-provider-owned-helper-shims",
...DEPRECATION_MARKING,
owner: "provider",
replacement: "provider-local auth, model, replay, OAuth, and stream helper APIs; retain until every helper is migrated in official providers and absent from published plugins",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"openclaw/plugin-sdk/provider-stream MOONSHOT_THINKING_STREAM_HOOKS",
"openclaw/plugin-sdk/provider-stream-shared defaultToolStreamExtraParams",
"openclaw/plugin-sdk/provider-stream-shared stripTrailingAnthropicAssistantPrefillWhenThinking",
"openclaw/plugin-sdk/provider-stream-shared createAnthropicThinkingPrefillPayloadWrapper",
"openclaw/plugin-sdk/provider-stream-shared OpenAICompatibleThinkingLevel",
"openclaw/plugin-sdk/provider-stream-shared isOpenAICompatibleThinkingEnabled",
"openclaw/plugin-sdk/provider-stream-shared DeepSeekV4ThinkingLevel",
"openclaw/plugin-sdk/provider-stream-shared DeepSeekV4ReasoningEffort",
"openclaw/plugin-sdk/provider-stream-shared createDeepSeekV4OpenAICompatibleThinkingWrapper",
"openclaw/plugin-sdk/provider-stream-shared createThinkingOnlyFinalTextWrapper",
"openclaw/plugin-sdk/provider-stream-shared createGoogleThinkingPayloadWrapper",
"openclaw/plugin-sdk/provider-stream-shared createGoogleThinkingStreamWrapper",
"openclaw/plugin-sdk/provider-model-shared isProxyReasoningUnsupportedModelHint",
"openclaw/plugin-sdk/provider-model-shared NATIVE_ANTHROPIC_REPLAY_HOOKS",
"openclaw/plugin-sdk/provider-model-shared PASSTHROUGH_GEMINI_REPLAY_HOOKS",
"openclaw/plugin-sdk/provider-auth toFormUrlEncoded",
"openclaw/plugin-sdk/provider-auth generatePkceVerifierChallenge",
"openclaw/plugin-sdk/provider-oauth-runtime OAuthProvider",
"openclaw/plugin-sdk/provider-oauth-runtime OAuthProviderInfo"
],
diagnostics: ["TypeScript @deprecated annotations naming provider-local replacements", "plugin boundary report compatibility inventory"],
tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Provider-specific auth, model, replay, OAuth, and stream shortcuts remain as deprecated SDK shims while providers move to their local APIs."
},
{
code: "message-presentation-legacy-bridges",
...DEPRECATION_MARKING,
owner: "channel",
replacement: "MessagePresentation values and channel presentation renderers; retain until reply producers and official channel packages no longer emit or read legacy interactive replies",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"InteractiveReplyButton.value",
"InteractiveReplyButton.url",
"InteractiveReplyButton.webApp",
"InteractiveReplyButton.web_app",
"InteractiveReplyOption.value",
"InteractiveReplyButton",
"InteractiveReplyOption",
"InteractiveReplyBlock",
"InteractiveReply",
"normalizeInteractiveReply",
"hasInteractiveReplyBlocks",
"presentationToInteractiveReply",
"presentationToInteractiveControlsReply",
"interactiveReplyToPresentation",
"resolveInteractiveTextFallback",
"src/auto-reply ReplyPayload.interactive",
"openclaw/plugin-sdk/reply-payload ReplyPayload.interactive",
"reduceInteractiveReply",
"@openclaw/discord buildDiscordInteractiveComponents",
"@openclaw/slack buildSlackInteractiveBlocks"
],
diagnostics: ["TypeScript @deprecated annotations naming MessagePresentation replacements", "plugin boundary report compatibility inventory"],
tests: [
"src/interactive/payload.test.ts",
"src/plugin-sdk/reply-payload.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Legacy interactive reply values and channel-specific rendering bridges remain available while producers migrate to MessagePresentation."
},
{
code: "plugin-sdk-focused-compat-aliases",
...DEPRECATION_MARKING,
owner: "sdk",
replacement: "the focused replacement named by each TypeScript @deprecated annotation; retain until every enumerated alias has zero bundled and published readers",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"openclaw/plugin-sdk/acp-runtime __testing",
"openclaw/plugin-sdk/channel-inbound BuildChannelTurnContextParams",
"openclaw/plugin-sdk/channel-inbound BuiltChannelTurnContext",
"openclaw/plugin-sdk/channel-inbound buildChannelTurnContext",
"openclaw/plugin-sdk/channel-inbound finalizeChannelInboundContext",
"openclaw/plugin-sdk/channel-inbound filterChannelTurnSupplementalContext",
"openclaw/plugin-sdk/channel-send-result ChannelSendRawResult",
"openclaw/plugin-sdk/keyed-async-queue KeyedAsyncQueue.getTailMapForTesting",
"openclaw/plugin-sdk/persistent-dedupe PersistentDedupeLegacyPathOptions.lockOptions",
"openclaw/plugin-sdk/retry-runtime createTelegramRetryRunner",
"openclaw/plugin-sdk/ssrf-policy SsrfPolicyOptions.allowPrivateNetwork",
"openclaw/plugin-sdk/ssrf-policy ssrfPolicyFromAllowPrivateNetwork",
"openclaw/plugin-sdk/tts-runtime TtsSynthesisStreamResult",
"openclaw/plugin-sdk/tts-runtime TtsRuntimeFacade._test"
],
diagnostics: ["TypeScript @deprecated annotations naming focused replacements", "plugin boundary report compatibility inventory"],
tests: [
"src/plugin-sdk/channel-inbound.test.ts",
"src/plugin-sdk/ssrf-policy.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Focused SDK compatibility aliases remain available through a dated window while callers adopt their annotated replacements."
},
{
code: "agent-harness-terminal-result-aliases",
...DEPRECATION_MARKING,
owner: "agent-runtime",
replacement: "AgentHarnessAttemptResult.terminal and AgentHarnessDeliveryDefaults.visibleReplies; retain until harness migration verifies that legacy terminal fields and sourceVisibleReplies are unread",
docsPath: "/plugins/sdk-agent-harness",
surfaces: [
"AgentHarnessAttemptResult.aborted",
"AgentHarnessAttemptResult.externalAbort",
"AgentHarnessAttemptResult.timedOut",
"AgentHarnessAttemptResult.idleTimedOut",
"AgentHarnessAttemptResult.timedOutDuringCompaction",
"AgentHarnessAttemptResult.timedOutDuringToolExecution",
"AgentHarnessAttemptResult.timedOutByRunBudget",
"AgentHarnessAttemptResult.promptError",
"AgentHarnessAttemptResult.promptErrorSource",
"AgentHarnessDeliveryDefaults.sourceVisibleReplies"
],
diagnostics: ["TypeScript @deprecated annotations on agent harness result and delivery defaults", "plugin boundary report compatibility inventory"],
tests: ["src/agents/harness/settled-turn-finalization-result.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Agent harness result booleans and sourceVisibleReplies remain available while harnesses migrate to terminal outcomes and visibleReplies."
},
{
code: "official-plugin-export-aliases",
...DEPRECATION_MARKING,
owner: "channel",
replacement: "MessagePresentation renderers and host-owned timeout/runtime behavior; retain until minimum supported official plugin packages no longer import these aliases",
docsPath: "/plugins/compatibility#current-compatibility-areas",
surfaces: [
"@openclaw/discord buildDiscordInteractiveComponents",
"@openclaw/discord normalizeDiscordListenerTimeoutMs",
"@openclaw/discord normalizeDiscordInboundWorkerTimeoutMs",
"@openclaw/discord isAbortError",
"@openclaw/discord runDiscordTaskWithTimeout",
"@openclaw/slack buildSlackInteractiveBlocks"
],
diagnostics: ["TypeScript @deprecated annotations on published official-plugin exports", "plugin boundary report compatibility inventory"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "Published channel presentation and Discord timeout aliases remain available while consumers move to their canonical exports and host-owned behavior."
},
{
code: "memory-host-compatibility-aliases",
...DEPRECATION_MARKING,
owner: "sdk",
replacement: "canonical memory cache/FTS tables; retain until supported memory integrations are verified to use canonical tables without overrides and legacy table data remains preserved",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: ["@openclaw/memory-host-sdk ensureMemoryIndexSchema.embeddingCacheTable", "@openclaw/memory-host-sdk ensureMemoryIndexSchema.ftsTable"],
diagnostics: ["TypeScript @deprecated annotations on memory-host SDK compatibility fields", "plugin boundary report memory-host SDK summary"],
tests: ["packages/memory-host-sdk/src/host/memory-schema.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Memory-host table overrides remain available until supported artifact usage and legacy data preservation are verified."
},
{
code: "plugin-runtime-api-compat-aliases",
...DEPRECATION_MARKING,
owner: "plugin-execution",
replacement: "the namespaced plugin API and focused runtime methods named per surface; retain until all enumerated flat API and runtime aliases have no readers",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"OpenClawPluginApi.registerSessionExtension",
"OpenClawPluginApi.enqueueNextTurnInjection",
"OpenClawPluginApi.registerControlUiDescriptor",
"OpenClawPluginApi.registerRuntimeLifecycle",
"OpenClawPluginApi.registerAgentEventSubscription",
"OpenClawPluginApi.emitAgentEvent",
"OpenClawPluginApi.setRunContext",
"OpenClawPluginApi.getRunContext",
"OpenClawPluginApi.clearRunContext",
"OpenClawPluginApi.registerSessionSchedulerJob",
"OpenClawPluginApi.registerSessionAction",
"OpenClawPluginApi.sendSessionAttachment",
"OpenClawPluginApi.scheduleSessionTurn",
"OpenClawPluginApi.unscheduleSessionTurnsByTag",
"PluginHookContext.senderExternalId",
"PluginAttachmentChannelHints.telegram",
"PluginAttachmentChannelHints.slack",
"AgentPromptSurfaceKind pi_main",
"PluginRuntime.channel.reply.createReplyDispatcherWithTyping",
"PluginRuntime.channel.reply.resolveHumanDelayConfig",
"PluginRuntime.channel.reply.dispatchReplyFromConfig",
"PluginRuntime.channel.reply.finalizeInboundContext",
"PluginRuntime.channel.media.fetchRemoteMedia",
"PluginRuntime.channel.session.resolveStorePath",
"PluginRuntime.channel.session.recordInboundSession",
"PluginRuntime.channel.inbound.runPreparedReply",
"PluginRuntime.channel.turn",
"PluginRuntime.system.requestHeartbeatNow"
],
diagnostics: ["TypeScript @deprecated annotations on plugin API and runtime aliases", "plugin boundary report compatibility inventory"],
tests: [
"src/plugins/captured-registration.test.ts",
"src/plugins/runtime/index.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Flat plugin registration and broad runtime aliases remain available while plugins migrate to namespaced APIs and focused runtime methods."
},
{
code: "plugin-provider-manifest-compat-aliases",
...DEPRECATION_MARKING,
owner: "provider",
replacement: "manifest-owned plugin kind/setup metadata and model catalog registration; retain until providers no longer publish runtime kind or legacy catalog hooks",
docsPath: "/plugins/sdk-migration#compatibility-policy",
surfaces: [
"DefinePluginEntryOptions.kind",
"SingleProviderPluginOptions.kind",
"OpenClawPluginDefinition.kind",
"PluginPackageChannel.cliAddOptions",
"ProviderPlugin.catalog",
"ProviderPlugin.staticCatalog",
"ProviderPlugin.suppressBuiltInModel",
"ProviderPlugin.augmentModelCatalog",
"ProviderBuiltInModelSuppressionContext"
],
diagnostics: ["TypeScript @deprecated annotations on plugin manifest and provider catalog aliases", "plugin boundary report compatibility inventory"],
tests: [
"src/plugins/contracts/package-manifest.contract.test.ts",
"src/plugins/contracts/provider-catalog-deprecation.contract.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Runtime plugin kind/setup metadata and provider catalog hooks remain available while plugins migrate ownership into manifests and catalog registrations."
}
];
//#endregion
//#region src/plugins/compat/media-legacy-projection.ts
/** Named compatibility contract for the shipped parallel media projection. */
const MEDIA_LEGACY_PROJECTION_COMPAT_RECORD = {
code: "media-legacy-projection",
status: "removal-pending",
owner: "sdk",
introduced: "2026-07-24",
deprecated: "2026-07-24",
warningStarts: "2026-07-24",
removeAfter: "2026-10-01",
replacement: "ordered `MsgContext.media` / `InboundMediaFacts[]`; typed hook `media` and `originalMedia`; `Attachment*` template variables; and `openclaw/plugin-sdk/media-local-roots`; retain until a clean published-plugin artifact sweep verifies that the legacy media surfaces have no readers",
docsPath: "/plugins/sdk-migration#media-legacy-projection",
surfaces: [
"MsgContext MediaPath/MediaUrl/MediaType and plural/staging fields",
"openclaw/plugin-sdk/agent-media-payload",
"ChannelInboundMediaPayload and buildChannelInboundMediaPayload",
"MediaPayload and buildMediaPayload",
"message hook mediaPath/mediaUrl/mediaType and plural/original metadata aliases",
"MediaPath/MediaUrl/MediaType/MediaDir template variables"
],
diagnostics: [
"TypeScript @deprecated annotations naming the facts-first replacement",
"plugin boundary report compatibility inventory with the approved removeAfter date",
"SDK, hook, and media template migration documentation"
],
tests: [
"src/sessions/user-turn-transcript.media.test.ts",
"src/hooks/message-hook-mappers.test.ts",
"src/media-understanding/runner.cli-audio.test.ts",
"src/plugins/compat/registry.test.ts",
"src/plugins/contracts/plugin-sdk-subpaths.test.ts"
],
releaseNote: "Legacy parallel media projections remain available as deprecated compatibility while plugins move to ordered facts, typed hook media, Attachment templates, and the focused media-local-roots SDK."
};
//#endregion
//#region src/plugins/compat/mention-inbox-record.ts
const MENTION_INBOX_COMPAT_RECORD = {
code: "mention-inbox-sync-persistence",
status: "deprecated",
owner: "sdk",
introduced: "2026-10-03",
deprecated: "2026-10-03",
warningStarts: "2026-10-03",
removalGate: "next-plugin-sdk-major",
replacement: "Await mentionInbox.listAsync(client, publish), mentionInbox.dismissAsync(client, ids, publish), mentionInbox.recordCommittedInputAsync(input), and mentionInbox.invalidateAsync(sessionKey). Publish list/dismiss responses synchronously inside the supplied callback. Retain synchronous methods only for shipped third-party contracts until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/how-to-migrate#await-mention-inbox-operations",
surfaces: [
"GatewayRequestHandlerOptions.context.mentionInbox.list",
"GatewayRequestHandlerOptions.context.mentionInbox.dismiss",
"GatewayRequestHandlerOptions.context.mentionInbox.recordCommittedInput",
"GatewayRequestHandlerOptions.context.mentionInbox.invalidate",
"getPluginRuntimeGatewayRequestScope().context.mentionInbox"
],
diagnostics: ["TypeScript @deprecated annotations and one DEP_SESSION_PERSISTENCE warning per plugin and method per process; unscoped callers warn once per method"],
tests: [
"src/plugins/compat/registry.test.ts",
"src/plugins/compat/mention-inbox-deprecation.test.ts",
"src/plugin-sdk/gateway-mention-inbox-compat.test.ts",
"src/gateway/mention-inbox.test.ts",
"src/gateway/mention-inbox.compat.test.ts"
],
releaseNote: "Mention Inbox reads, dismissals, recording, and invalidation expose awaited worker-backed methods. Synchronous plugin methods retain their existing return values and completion timing until the next Plugin SDK major; stored data, retention, and update behavior are unchanged."
};
//#endregion
//#region src/plugins/compat/plugin-sdk-subpath-records.ts
const PLUGIN_SDK_SUBPATH_SEEDS = [
{
code: "plugin-sdk-channel-streaming-subpath",
subpath: "channel-streaming",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-outbound`",
releaseNote: "The deprecated `channel-streaming` Plugin SDK subpath was removed; plugins now import channel streaming helpers from `channel-outbound`."
},
{
code: "plugin-sdk-config-runtime-subpath",
subpath: "config-runtime",
status: "removed",
owner: "config",
replacement: "`api.pluginConfig`, `openclaw/plugin-sdk/config-mutation`, `openclaw/plugin-sdk/runtime-config-snapshot`, and `openclaw/plugin-sdk/config-contracts`",
releaseNote: "The deprecated `config-runtime` Plugin SDK facade was removed by the approved September 30 retirement; migrate to `api.pluginConfig`, `openclaw/plugin-sdk/config-mutation`, `openclaw/plugin-sdk/runtime-config-snapshot`, and `openclaw/plugin-sdk/config-contracts`."
},
{
code: "plugin-sdk-inbound-reply-dispatch-subpath",
subpath: "inbound-reply-dispatch",
owner: "channel",
removalGate: "next-plugin-sdk-major",
replacement: "`openclaw/plugin-sdk/channel-inbound` and `openclaw/plugin-sdk/channel-outbound`"
},
{
code: "plugin-sdk-channel-reply-pipeline-subpath",
subpath: "channel-reply-pipeline",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-outbound`",
releaseNote: "The deprecated `channel-reply-pipeline` Plugin SDK facade was removed by the approved September 30 retirement; migrate to `openclaw/plugin-sdk/channel-outbound`."
},
{
code: "plugin-sdk-infra-runtime-subpath",
subpath: "infra-runtime",
status: "removed",
owner: "sdk",
replacement: "focused typed subpaths including `openclaw/plugin-sdk/system-event-runtime`, `openclaw/plugin-sdk/error-runtime`, `openclaw/plugin-sdk/approval-runtime`, and `openclaw/plugin-sdk/ssrf-runtime`; use injected runtime APIs for host operations without a public subpath",
releaseNote: "The deprecated `infra-runtime` Plugin SDK facade was removed by the approved September 30 retirement; migrate to focused typed subpaths including `openclaw/plugin-sdk/system-event-runtime`, `openclaw/plugin-sdk/error-runtime`, `openclaw/plugin-sdk/approval-runtime`, and `openclaw/plugin-sdk/ssrf-runtime`; use injected runtime APIs for host operations without a public subpath."
},
{
code: "plugin-sdk-text-runtime-subpath",
subpath: "text-runtime",
status: "removed",
owner: "sdk",
replacement: "`openclaw/plugin-sdk/logging-core`, `openclaw/plugin-sdk/text-chunking`, `openclaw/plugin-sdk/text-utility-runtime`, and `openclaw/plugin-sdk/string-coerce-runtime`",
releaseNote: "The deprecated `text-runtime` Plugin SDK facade was removed; plugins now import logging, chunking, text utility, and string coercion helpers from their focused subpaths."
},
{
code: "plugin-sdk-channel-secret-runtime-subpath",
subpath: "channel-secret-runtime",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-secret-basic-runtime` and `openclaw/plugin-sdk/channel-secret-tts-runtime`",
releaseNote: "The deprecated `channel-secret-runtime` Plugin SDK subpath was removed; plugins now use the focused basic and TTS secret-runtime subpaths."
},
{
code: "plugin-sdk-agent-config-primitives-subpath",
subpath: "agent-config-primitives",
status: "removed",
owner: "config",
replacement: "`openclaw/plugin-sdk/channel-config-schema`",
releaseNote: "The deprecated `agent-config-primitives` Plugin SDK subpath was removed; plugins now use maintained config-schema primitives."
},
{
code: "plugin-sdk-matrix-subpath",
subpath: "matrix",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/run-command`",
releaseNote: "The deprecated `matrix` Plugin SDK facade was removed; command execution now uses the generic `run-command` subpath."
},
{
code: "plugin-sdk-channel-logging-subpath",
subpath: "channel-logging",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-inbound` and `openclaw/plugin-sdk/channel-outbound`",
releaseNote: "The deprecated `channel-logging` Plugin SDK subpath was removed; channel logging helpers now come from the inbound and outbound channel surfaces."
},
{
code: "plugin-sdk-channel-lifecycle-subpath",
subpath: "channel-lifecycle",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-outbound`",
releaseNote: "The deprecated `channel-lifecycle` Plugin SDK facade was removed by the approved September 30 retirement; migrate to `openclaw/plugin-sdk/channel-outbound`."
},
{
code: "plugin-sdk-channel-message-subpath",
subpath: "channel-message",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-outbound` and `openclaw/plugin-sdk/channel-inbound`",
releaseNote: "The deprecated `channel-message` Plugin SDK facade was removed by the approved September 30 retirement; migrate to `openclaw/plugin-sdk/channel-outbound` and `openclaw/plugin-sdk/channel-inbound`."
},
{
code: "plugin-sdk-group-access-subpath",
subpath: "group-access",
status: "removed",
owner: "channel",
replacement: "`openclaw/plugin-sdk/channel-ingress-runtime`",
releaseNote: "The deprecated `group-access` Plugin SDK subpath was removed; plugins now resolve message admission through `channel-ingress-runtime`."
},
{
code: "plugin-sdk-zod-subpath",
subpath: "zod",
status: "removed",
owner: "sdk",
replacement: "the direct `zod` package import",
releaseNote: "The deprecated `zod` Plugin SDK re-export was removed; plugins now import `zod` directly."
}
];
function buildPluginSdkSubpathRecord(seed) {
const shared = {
code: seed.code,
owner: seed.owner,
introduced: "2026-07-06",
replacement: seed.replacement,
docsPath: "/plugins/sdk-migration",
surfaces: [`openclaw/plugin-sdk/${seed.subpath}`],
tests: ["src/plugins/compat/registry.test.ts"]
};
if ("status" in seed && seed.status === "removed") return {
...shared,
status: seed.status,
diagnostics: ["plugin SDK compatibility registry and migration guide"],
releaseNote: seed.releaseNote
};
return {
...shared,
status: "status" in seed ? seed.status : "deprecated",
deprecated: "2026-07-06",
warningStarts: "2026-07-06",
removalGate: "removalGate" in seed ? seed.removalGate : void 0,
diagnostics: ["repository deprecated API usage guard for core and bundled plugins; no external runtime import warning"]
};
}
const PLUGIN_SDK_SUBPATH_RECORDS = PLUGIN_SDK_SUBPATH_SEEDS.map(buildPluginSdkSubpathRecord);
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS = [
{
subpath: "media-understanding",
status: "removal-pending",
removeAfter: "2026-09-30",
replacement: "`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`; retain the public subpath through the 2026-09-30 window while official plugin consumers migrate",
docsPath: "/plugins/architecture"
},
{
subpath: "memory-host-core",
status: "removal-pending",
removeAfter: "2026-09-30",
replacement: "host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade through the 2026-09-30 window and until a focused public-artifact read seam exists",
docsPath: "/plugins/architecture-internals#context-engine-plugins"
},
{
subpath: "plugin-config-runtime",
status: "removal-pending",
removeAfter: "2026-12-01",
replacement: "`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths; retain the public subpath through the 2026-12-01 window while official plugin consumers migrate",
docsPath: "/plugins/sdk-runtime"
},
{
subpath: "tool-plugin",
status: "deprecated",
replacement: "retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`; `getToolPluginMetadata` currently reads metadata only from an already-executed entry",
docsPath: "/plugins/tool-plugins"
}
];
function buildPublicSdkSubpathRecord({ subpath, ...compat }) {
return {
code: `plugin-sdk-${subpath}-public-demotion`,
owner: "sdk",
introduced: "2026-07-15",
deprecated: "2026-07-15",
warningStarts: "2026-07-15",
...compat,
surfaces: [`openclaw/plugin-sdk/${subpath}`],
diagnostics: ["registry-backed public SDK demotion window; no external runtime import warning"],
tests: ["src/plugins/compat/registry.test.ts"]
};
}
const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS.map(buildPublicSdkSubpathRecord);
//#endregion
//#region src/plugins/compat/session-persistence-records.ts
const SESSION_PERSISTENCE_COMPAT_RECORDS = [
{
code: "native-session-generation-sync-authority",
status: "deprecated",
owner: "agent-runtime",
introduced: "2026-09-22",
deprecated: "2026-10-03",
warningStarts: "2026-10-03",
removalGate: "next-plugin-sdk-major",
replacement: "Await prepareNativeSessionGenerationAuthority, resolveNativeSessionBindingWithAuthority, and reclaimNativeSessionGenerationWithAuthority with NativeSessionGenerationOperationsV2. Retain released synchronous capture and two-argument mutation callbacks until published official harness readers migrate and a breaking release is explicitly approved.",
docsPath: "/plugins/sdk-migration/compatibility-policy#native-session-generation-authority",
surfaces: [
"captureNativeSessionGenerationAuthority",
"resolveNativeSessionBinding",
"reclaimNativeSessionGeneration",
"NativeSessionGenerationOperations"
],
diagnostics: ["TypeScript @deprecated annotations and migration documentation; no runtime warnings"],
tests: [
"src/plugin-sdk/agent-harness-session-compat.test.ts",
"src/agents/harness/native-session/binding-generation.test.ts",
"src/agents/harness/native-session/binding-generation-authority.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Native harnesses can admit binding operations through worker-backed session authority. Released official harness plugins retain synchronous authority capture and lineage-checking mutation callbacks across host upgrades."
},
{
code: "session-manager-sync-persistence",
status: "deprecated",
owner: "sdk",
introduced: "2026-10-01",
deprecated: "2026-10-01",
warningStarts: "2026-10-01",
removalGate: "next-plugin-sdk-major",
replacement: "Await the matching Async-suffixed SessionManager method, including the returned rewrite commit. Retain synchronous adapters only for shipped third-party contracts until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/how-to-migrate#await-session-transcript-persistence",
surfaces: [
"SessionManager.appendMessage",
"SessionManager.appendMessageWithTranscriptAnchor",
"SessionManager.appendCompaction",
"SessionManager.appendResetBoundary",
"SessionManager.appendCustomEntry",
"SessionManager.appendSessionInfo",
"SessionManager.appendCustomMessageEntry",
"SessionManager.appendLeafControl",
"SessionManager.appendLabelChange",
"SessionManager.branch",
"SessionManager.branchWithSummary",
"SessionManager.removeTrailingEntries",
"SessionManager.persist",
"SessionManager.prepareTranscriptRewrite",
"SessionManager.appendMessageToTranscript",
"SessionManager.open",
"SessionManager.openBounded",
"SessionManager.openDetachedBounded",
"SessionManager.openModelContext",
"SessionManager.setSessionTarget",
"SessionManager.reloadPersistedTranscript"
],
diagnostics: ["TypeScript @deprecated annotations naming awaited twins", "one runtime DEP_SESSION_PERSISTENCE warning per method per process"],
tests: [
"src/plugins/compat/registry.test.ts",
"src/agents/sessions/session-manager-async-entries.test.ts",
"src/agents/sessions/session-manager-async-message.test.ts",
"src/agents/sessions/session-manager-maintenance-async.test.ts"
],
releaseNote: "Plugins can await SessionManager transcript mutations through the existing SQLite worker. Synchronous methods retain their shipped return values as deprecated third-party adapters until the next Plugin SDK major; storage formats and update behavior are unchanged."
},
{
code: "extension-session-sync-persistence",
status: "deprecated",
owner: "sdk",
introduced: "2026-10-01",
deprecated: "2026-10-01",
warningStarts: "2026-10-01",
removalGate: "next-plugin-sdk-major",
replacement: "Await ExtensionAPI.appendEntryAsync, setSessionNameAsync, and setLabelAsync, and AgentSession.setSessionNameAsync. Existing synchronous third-party methods retain their void return contract until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/how-to-migrate#await-extension-session-changes",
surfaces: [
"ExtensionAPI.appendEntry",
"ExtensionAPI.setSessionName",
"ExtensionAPI.setLabel",
"AgentSession.setSessionName"
],
diagnostics: ["TypeScript @deprecated annotations, migration guide, and once-per-method DEP_SESSION_PERSISTENCE warning"],
tests: ["src/plugins/compat/registry.test.ts", "src/agents/sessions/sdk.metadata-admission.test.ts"],
releaseNote: "Extensions can await transcript entries, session names, and labels; the shipped synchronous methods remain third-party compatibility adapters through the next Plugin SDK major."
},
{
code: "provider-replay-sync-persistence",
status: "deprecated",
owner: "provider",
introduced: "2026-10-01",
deprecated: "2026-10-01",
warningStarts: "2026-10-01",
removalGate: "next-plugin-sdk-major",
replacement: "Use ProviderPlugin.sanitizeReplayHistoryAsync with ProviderSanitizeReplayHistoryContextV2 and await ProviderReplaySessionStateV2.appendCustomEntryAsync; use sanitizeGoogleGeminiReplayHistoryAsync for the shared Gemini implementation. Retain legacy third-party contexts and hooks until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/how-to-migrate#await-provider-replay-metadata",
surfaces: [
"ProviderPlugin.sanitizeReplayHistory",
"ProviderReplaySessionState.appendCustomEntry",
"sanitizeGoogleGeminiReplayHistory"
],
diagnostics: ["TypeScript @deprecated annotations, versioned migration guide, and once-per-method DEP_SESSION_PERSISTENCE warning"],
tests: [
"src/plugins/compat/registry.test.ts",
"src/plugins/provider-replay-helpers.test.ts",
"src/plugins/provider-runtime.test.ts",
"src/plugin-sdk/provider-model-shared.test.ts"
],
releaseNote: "Provider replay hooks can await committed transcript metadata through additive V2 context types. Legacy hooks, context types, and the synchronous Gemini helper remain available for third-party migration through the next Plugin SDK major."
}
];
//#endregion
//#region src/plugins/compat/watched-sessions.ts
const WATCHED_SESSIONS_COMPAT_RECORD = {
code: "watched-sessions-sync-harness-context",
status: "deprecated",
owner: "sdk",
introduced: "2026-07-29",
deprecated: "2026-10-03",
warningStarts: "2026-10-03",
removalGate: "next-plugin-sdk-major",
replacement: "Await prepareWatchedSessionsHarnessContext with an assertCurrent callback bound to the current host capability. The released buildWatchedSessionsHarnessContext retains its synchronous string-or-undefined result until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/compatibility-policy#watched-session-harness-context",
surfaces: ["openclaw/plugin-sdk/agent-harness-runtime.buildWatchedSessionsHarnessContext"],
diagnostics: ["TypeScript @deprecated annotation and migration documentation; no runtime warnings"],
tests: [
"src/plugins/compat/registry.test.ts",
"src/plugin-sdk/agent-harness-runtime-watched-sessions.test.ts",
"src/agents/watched-sessions-prompt.test.ts"
],
releaseNote: "Harness plugins can await watched-session prompt preparation from worker reads. Bundled harnesses use the awaited helper; the synchronous SDK result remains supported for third-party plugins. Prompt bytes, stored data, and update behavior are unchanged."
};
//#endregion
//#region src/plugins/compat/workspace-mutation-guard.ts
const WORKSPACE_MUTATION_GUARD_COMPAT_RECORD = {
code: "workspace-mutation-guard-callback",
status: "deprecated",
owner: "sdk",
introduced: "2026-10-02",
deprecated: "2026-10-02",
warningStarts: "2026-10-02",
removalGate: "next-plugin-sdk-major",
replacement: "Await database preparation before ensureAgentWorkspace and use SQL-free guard.assertHost for live authority. Internal recovery predicates run on the worker transaction connection.",
docsPath: "/plugins/sdk-migration/how-to-migrate#workspace-mutation-guards",
surfaces: ["api.runtime.agent.ensureAgentWorkspace.beforePersistentApply"],
diagnostics: ["@deprecated JSDoc and one DEP_WORKSPACE_MUTATION_GUARD warning per process", "Warning explains before-dispatch timing and deprecated but allowed synchronous OpenClaw DB access"],
tests: ["src/plugins/runtime/runtime-agent.workspace.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Released callbacks and their synchronous OpenClaw DB access remain supported. The legacy check runs once before worker dispatch, outside admission grants; use the typed guard for live revocation at commit. Removal requires the next Plugin SDK major."
};
//#endregion
//#region src/plugins/compat/registry-records.ts
const ACTIVATION_HINT_METADATA = {
status: "active",
owner: "plugin-execution",
introduced: "2026-04-24",
docsPath: "/plugins/manifest",
diagnostics: ["activation plan compat reason"],
tests: ["src/plugins/activation-planner.test.ts"]
};
const PLUGIN_COMPAT_RECORDS = [
MENTION_INBOX_COMPAT_RECORD,
WORKSPACE_MUTATION_GUARD_COMPAT_RECORD,
...SESSION_PERSISTENCE_COMPAT_RECORDS,
...AGENT_LIST_RUNTIME_PROJECTION_COMPAT_RECORDS,
WATCHED_SESSIONS_COMPAT_RECORD,
{
code: "gateway-placement-sync-results",
status: "deprecated",
owner: "sdk",
introduced: "2026-09-07",
deprecated: "2026-10-02",
warningStarts: "2026-10-02",
removalGate: "next-plugin-sdk-major",
replacement: "Await listPendingWorkspaceResultsAsync, getWorkspaceResultReconcilingSessionIdsAsync, and deferOrphanedRequestsAsync on the Gateway context. Released synchronous methods retain their return values and completion timing until the next Plugin SDK major and explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/compatibility-policy#gateway-placement-and-publication-readers",
surfaces: [
"GatewayRequestHandlerOptions.context.workerSessionPlacementService.listPendingWorkspaceResults",
"GatewayRequestHandlerOptions.context.workerSessionPlacementService.getWorkspaceResultReconcilingSessionIds",
"GatewayRequestHandlerOptions.context.githubPublicationService.deferOrphanedRequests",
"getPluginRuntimeGatewayRequestScope().context"
],
diagnostics: ["TypeScript @deprecated annotations and migration documentation; no runtime warnings"],
tests: [
"src/plugin-sdk/gateway-placement-compat.test.ts",
"src/gateway/worker-environments/placement-store.test.ts",
"src/gateway/github-publication-boundaries.test.ts",
"src/gateway/github-repository-publication.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "Placement result readers and GitHub orphan deferral expose awaited methods while retaining the synchronous Gateway-context contracts shipped to plugins in 2026.9.7. Internal placement readers use the SQLite worker; stored data and update behavior are unchanged."
},
{
code: "memory-session-sync-inventory",
status: "deprecated",
owner: "sdk",
introduced: "2026-08-09",
deprecated: "2026-10-01",
warningStarts: "2026-10-01",
removalGate: "next-plugin-sdk-major",
replacement: "Await loadArchivedSessionsAsync and resolveMemorySessionTargetsAsync from memory-core-host-engine-sessions. Synchronous readers retain their existing signatures and results until the next Plugin SDK major.",
docsPath: "/plugins/sdk-migration/compatibility-policy#memory-session-inventory-readers",
surfaces: ["loadArchivedSessions", "resolveMemorySessionTargets"],
diagnostics: ["TypeScript @deprecated annotations and migration documentation; no runtime warnings"],
tests: [
"src/plugin-sdk/memory-core-host-engine-sessions.test.ts",
"src/plugins/compat/registry.test.ts",
"extensions/memory-core/src/memory-forget.participants.test.ts"
],
releaseNote: "Memory archive discovery and forget target selection can be awaited through worker-backed SDK readers; synchronous readers remain compatible until the next Plugin SDK major."
},
{
code: "channel-webhook-listener-config-inputs",
status: "deprecated",
owner: "config",
introduced: "2026-09-26",
deprecated: "2026-09-26",
warningStarts: "2026-09-26",
removalGate: "next-plugin-sdk-major",
replacement: "Use legacyWebhook for canonical listener config. Doctor migrates stored webhookPort/webhookHost and webhook.port inputs; deprecated optional TypeScript properties preserve config producer source compatibility until an approved SDK major removal.",
docsPath: "/gateway/doctor/config-migrations#channel-webhook-listeners",
surfaces: [
"TelegramAccountConfig.webhookPort",
"TelegramAccountConfig.webhookHost",
"FeishuConfig.webhookPort",
"FeishuConfig.webhookHost",
"FeishuAccountConfig.webhookPort",
"FeishuAccountConfig.webhookHost",
"MSTeamsWebhookConfig.port",
"NextcloudTalkAccountConfig.webhookPort",
"NextcloudTalkAccountConfig.webhookHost"
],
diagnostics: ["TypeScript @deprecated annotations and plugin-owned Doctor migration"],
tests: [
"extensions/telegram/src/doctor.test.ts",
"extensions/feishu/src/doctor-contract.test.ts",
"extensions/msteams/config-doctor-api.test.ts",
"extensions/nextcloud-talk/src/doctor-contract.test.ts"
]
},
{
code: "conversation-binding-sync-mutations",
status: "deprecated",
owner: "channel",
introduced: "2026-09-20",
deprecated: "2026-09-20",
warningStarts: "2026-09-20",
removalGate: "next-plugin-sdk-major",
replacement: "Await getSessionBindingService().inspectByConversationAsync, resolveByConversationAsync, touchAsync, resolveRuntimeConversationBindingRouteAsync, and the Async-suffixed thread-binding lifecycle setters. Project prepared inspection facts with inspectRuntimeConversationBindingRoute. Async dispatch retains an explicit synchronous fallback for legacy external adapters; remaining bind/unbind and other storage operations are separate migration work.",
docsPath: "/plugins/sdk-runtime/channel#awaited-conversation-binding-mutations",
surfaces: [
"SessionBindingService.touch",
"SessionBindingService.resolveByConversation",
"SessionBindingAdapter.touch",
"SessionBindingAdapter.resolveByConversation",
"resolveRuntimeConversationBindingRoute",
"ChannelConversationBindingSupport.setIdleTimeoutBySessionKey",
"ChannelConversationBindingSupport.setMaxAgeBySessionKey",
"api.runtime.channel.threadBindings.setIdleTimeoutBySessionKey",
"api.runtime.channel.threadBindings.setMaxAgeBySessionKey"
],
diagnostics: ["TypeScript @deprecated annotations on synchronous methods; resolver migration is recorded here and in docs while its broad barrel remains deprecated; no runtime warnings"],
tests: [
"src/infra/outbound/session-binding-service.test.ts",
"src/channels/plugins/binding-routing.test.ts",
"src/channels/plugins/conversation-bindings.test.ts"
],
releaseNote: "Plugins can expose explicitly awaited binding mutations and pure ownership inspection; synchronous public methods remain supported while callers and persistence owners migrate."
},
...PLUGIN_SDK_SUBPATH_RECORDS,
...BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS,
...DEPRECATION_MARKING_COMPAT_RECORDS,
MEDIA_LEGACY_PROJECTION_COMPAT_RECORD,
{
code: "node-workspace-sync-acquisition",
status: "deprecated",
owner: "sdk",
introduced: "2026-08-21",
deprecated: "2026-09-15",
warningStarts: "2026-09-15",
removalGate: "next-plugin-sdk-major",
replacement: "Await context.acquireManagedWorkspaceAsync(request) and release the returned lease in finally. Retain synchronous acquisition for supported external plugins until explicit breaking-release approval.",
docsPath: "/plugins/sdk-migration/how-to-migrate#managed-node-workspace-acquisition",
surfaces: ["OpenClawPluginNodeHostCommandContext.acquireManagedWorkspace"],
diagnostics: ["TypeScript @deprecated annotation and migration documentation; no runtime warnings"],
tests: [
"src/node-host/invoke-workspace.test.ts",
"src/node-host/node-worker-workspace-retention.test.ts",
"extensions/codex/src/node-exec-server.test.ts"
],
releaseNote: "Node-host plugins can await managed workspace acquisition while existing synchronous callers retain their immediate lease contract."
},
{
code: "plugin-state-sync-keyed-store",
status: "deprecated",
owner: "sdk",
introduced: "2026-05-29",
deprecated: "2026-09-11",
warningStarts: "2026-09-11",
removalGate: "next-plugin-sdk-major",
replacement: "`api.runtime.state.openKeyedStore` and `PluginStateKeyedStore`; await operations while keeping transactional callbacks synchronous. Retain the sync adapter until a supported external-plugin migration and explicit breaking-release approval.",
docsPath: "/plugins/sdk-runtime/state-and-system#synchronous-keyed-store-migration",
surfaces: [
"api.runtime.state.openSyncKeyedStore",
"PluginStateSyncKeyedStore",
"createPluginStateSyncKeyedStore",
"PluginStateKeyedStore.update",
"PluginStateKeyedStore.deleteIf"
],
diagnostics: ["TypeScript @deprecated annotations and state-store migration documentation", "plugin compatibility inventory; no new runtime warnings"],
tests: [
"src/plugins/compat/registry.test.ts",
"src/plugin-state/plugin-state-store.test.ts",
"src/plugin-state/plugin-state-store.runtime.test.ts",
"test/type-contracts/plugin-state-store-runtime.ts",
"src/plugins/loader.runtime-registry.test.ts"
],
releaseNote: "Synchronous plugin keyed stores remain supported through the next Plugin SDK major while plugins migrate to awaited keyed-store operations; trust eligibility and transactional callbacks are unchanged."
},
{
code: "memory-read-result-statusless-success",
status: "deprecated",
owner: "sdk",
introduced: "2026-04-28",
deprecated: "2026-08-19",
warningStarts: "2026-08-19",
removalGate: "next-plugin-sdk-major",
replacement: "`MemoryReadResult` with explicit `status: \"ok\" | \"not_found\"`",
docsPath: "/plugins/sdk-migration#memory-read-missing-results",
surfaces: ["statusless external memory manager read results"],
diagnostics: ["host memory-manager acquisition adapter"],
tests: ["src/plugins/memory-runtime.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "External memory managers must return explicit not-found status for absence; statusless results retain legacy successful-read semantics through the next Plugin SDK major."
},
{
code: "context-engine-legacy-host-param-default",
status: "removed",
owner: "sdk",
introduced: "2026-07-29",
replacement: "`ContextEngineInfo.acceptedHostParams` for restricted projection; omitted declarations receive full host params",
docsPath: "/concepts/context-engine#the-contextengine-interface",
surfaces: ["ContextEngineInfo.acceptedHostParams and undeclared-engine default projection"],
diagnostics: ["plugin compatibility registry and context engine guide"],
tests: ["src/context-engine/host-param-projection.test.ts"],
releaseNote: "The undeclared context-engine host-parameter compatibility default was removed; engines without `acceptedHostParams` now receive all current host fields."
},
{
code: "removed-global-api-provider-publication",
status: "removed",
owner: "sdk",
introduced: "2026-05-27",
replacement: "provider plugins via `api.registerProvider(...)`; host/runtime code registers against its lifecycle-owned `ApiRegistry`",
docsPath: "/plugins/sdk-migration#process-global-api-provider-publication",
surfaces: ["openclaw/plugin-sdk/llm registerApiProvider", "openclaw/plugin-sdk/llm unregisterApiProviders"],
diagnostics: ["plugin SDK compatibility registry and migration guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "The process-global API-provider publication facade was removed; provider plugins now publish through their lifecycle-owned registration, and host runtimes register directly on their prepared ApiRegistry."
},
{
code: "legacy-deactivate-hook-alias",
status: "removed",
owner: "sdk",
introduced: "2026-05-16",
replacement: "`gateway_stop` hook",
docsPath: "/plugins/sdk-migration#deactivate-hook-alias",
surfaces: ["api.on(\"deactivate\", ...)", "plugin typed hook registration"],
diagnostics: ["plugin compatibility registry and migration guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "The deprecated `api.on(\"deactivate\", ...)` hook alias was removed; plugins must register cleanup with `gateway_stop`."
},
{
code: "legacy-subagent-spawning-hook",
status: "removed",
owner: "sdk",
introduced: "2026-05-30",
replacement: "`subagent_spawned` for post-launch observation; core session-binding adapters for thread routing",
docsPath: "/plugins/hooks#upcoming-deprecations",
surfaces: [
"api.on(\"subagent_spawning\", ...)",
"PluginHookSubagentSpawningEvent",
"PluginHookSubagentSpawningResult",
"SubagentLifecycleHookRunner.runSubagentSpawning"
],
diagnostics: ["plugin compatibility registry and migration guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "`api.on(\"subagent_spawning\", ...)` was removed; core now owns thread-bound subagent routing, and `subagent_spawned` remains available for observation."
},
{
code: "hook-only-plugin-shape",
status: "active",
owner: "sdk",
introduced: "2026-04-24",
replacement: "explicit capability registration",
docsPath: "/plugins/sdk-migration",
surfaces: [
"plugin shape inspection",
"plugins inspect",
"status diagnostics"
],
diagnostics: ["plugin compatibility notice"],
tests: ["src/plugins/status.test.ts", "src/plugins/contracts/shape.contract.test.ts"]
},
{
code: "deprecated-memory-embedding-provider-api",
status: "removed",
owner: "sdk",
introduced: "2026-05-21",
replacement: "`api.registerEmbeddingProvider(...)` and `contracts.embeddingProviders`",
docsPath: "/plugins/sdk-migration#memory-embedding-provider-api",
surfaces: [
"api.registerMemoryEmbeddingProvider(...)",
"contracts.memoryEmbeddingProviders",
"openclaw/plugin-sdk/memory-core-host-engine-embeddings registerMemoryEmbeddingProvider",
"plugin compatibility registry and migration guide"
],
diagnostics: ["plugin compatibility registry and migration guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "Memory-specific embedding provider registration was removed; plugins now use the generic embedding provider contract."
},
{
code: "deprecated-session-store-beta5-api",
status: "removed",
owner: "sdk",
introduced: "2026-05-21",
deprecated: "2026-07-12",
warningStarts: "2026-07-12",
replacement: "Use `getSessionEntry(...)` and `listSessionEntries(...)` for reads; `patchSessionEntry(...)`, `upsertSessionEntry(...)`, and `deleteSessionEntry(...)` for row mutations; and session identity with `session-transcript-runtime` for active transcripts. The supported-plugin cutoff excludes v2026.7.1-beta.5 and other packages importing the retired bridge.",
docsPath: "/plugins/sdk-migration/removed-surfaces#removed-session-and-transcript-file-apis",
surfaces: [
"openclaw/plugin-sdk/session-store-runtime loadSessionStore",
"openclaw/plugin-sdk/session-store-runtime updateSessionStore",
"openclaw/plugin-sdk/session-store-runtime resolveSessionFilePath",
"openclaw/plugin-sdk/session-store-runtime resolveSessionStoreEntry",
"openclaw/plugin-sdk/session-store-runtime LoadSessionStoreOptions",
"openclaw/plugin-sdk/session-store-runtime UpdateSessionStoreOptions",
"openclaw package root loadSessionStore",
"openclaw package root saveSessionStore"
],
diagnostics: ["plugin compatibility registry and migration guide"],
tests: [
"src/plugin-sdk/session-store-runtime.test.ts",
"src/index.test.ts",
"src/plugins/compat/registry.test.ts"
],
releaseNote: "The September 30, 2026 approved cutoff retired the beta.5 session-store bridge, its option types, and package-root loadSessionStore/saveSessionStore aliases ahead of the former October 12 window. Plugins must use scoped row APIs and identity-backed transcript APIs; the session-store-runtime subpath and resolveStorePath remain available."
},
{
code: "plugin-sdk-session-agent-resolution-aliases",
status: "deprecated",
owner: "sdk",
introduced: "2026-08-29",
deprecated: "2026-08-29",
warningStarts: "2026-08-29",
removeAfter: "2026-11-29",
replacement: "`resolveSessionAgentIdsStrict` and `resolveSessionAgentIdStrict` with an explicit agent, agent-scoped session key, prepared fallback, or persisted owner",
docsPath: "/plugins/compatibility#session-agent-resolution-aliases",
surfaces: [
"openclaw/plugin-sdk/agent-scope-runtime resolveSessionAgentIds and resolveSessionAgentId",
"openclaw/plugin-sdk/agent-runtime session-agent resolver aliases",
"openclaw/plugin-sdk/agent-harness-runtime session-agent resolver aliases",
"openclaw/plugin-sdk/memory-core-host-runtime-core session-agent resolver alias",
"openclaw/plugin-sdk/memory-host-core session-agent resolver alias"
],
diagnostics: ["TypeScript deprecated SDK alias annotations", "plugin compatibility registry"],
tests: ["src/plugin-sdk/agent-scope-runtime.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Legacy Plugin SDK session-agent resolver names preserve ambient system-agent fallback while published plugins migrate to strict owner-required aliases."
},
{
code: "agent-harness-credential-prompt-string-argument",
status: "deprecated",
owner: "sdk",
introduced: "2026-08-08",
deprecated: "2026-09-09",
warningStarts: "2026-09-09",
removeAfter: "2026-11-30",
replacement: "options object `{ controlToolsAvailable }`",
docsPath: "/plugins/sdk-migration/removed-surfaces#credential-prompt-builder",
surfaces: ["openclaw/plugin-sdk/agent-harness-runtime buildCredentialSafetyPrompt string argument"],
diagnostics: ["JSDoc parameter deprecation", "plugin compatibility registry"],
tests: ["src/agents/credential-safety-prompt.test.ts"],
releaseNote: "The credential prompt helper remains available with private login-code handoff and capability-aware terminal setup guidance; its ignored legacy string argument is supported through 2026-11-30."
},
{
code: "removed-session-transcript-file-api",
status: "removed",
owner: "sdk",
introduced: "2026-07-01",
replacement: "session identity (`sessionKey`/`sessionId`), `SessionTranscriptUpdate.target`, and Gateway/runtime session helpers",
docsPath: "/plugins/sdk-migration#removed-session-and-transcript-file-apis",
surfaces: [
"saveSessionStore",
"resolveSessionTranscriptPathInDir",
"resolveAndPersistSessionFile",
"readLatestAssistantTextFromSessionTranscript",
"SessionTranscriptUpdate.sessionFile",
"sessionFiles",
"transcriptPath",
"sessionFile",
"plugins inspect compatibility notices"
],
diagnostics: ["plugin compatibility notice"],
tests: ["src/plugins/status.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Session/transcript file APIs were removed with the SQLite session storage flip; plugins now use session identity and Gateway/runtime session helpers."
},
{
code: "hook.before_tool_call.terminal-block-approval",
status: "active",
owner: "agent-runtime",
introduced: "2026-04-29",
docsPath: "/plugins/hooks",
surfaces: ["before_tool_call block result", "before_tool_call approval result"],
diagnostics: ["hook runner contract probe"],
tests: ["src/agents/agent-tools.before-tool-call.e2e.test.ts"]
},
{
code: "hook.llm-observer.privacy-payload",
status: "active",
owner: "agent-runtime",
introduced: "2026-04-29",
docsPath: "/plugins/hooks",
surfaces: [
"llm_input",
"llm_output",
"agent_end",
"allowConversationAccess"
],
diagnostics: ["conversation access hook contract probe"],
tests: ["src/agents/cli-runner.reliability.test.ts", "src/config/schema.help.quality.test.ts"]
},
{
code: "api.capture.runtime-registrars",
status: "active",
owner: "plugin-execution",
introduced: "2026-04-29",
docsPath: "/plugins/architecture-internals",
surfaces: [
"createCapturedPluginRegistration",
"capturePluginRegistration",
"OpenClawPluginApi"
],
diagnostics: ["runtime registration capture contract probe"],
tests: ["src/plugins/captured-registration.test.ts"]
},
{
code: "channel.runtime.envelope-config-metadata",
status: "active",
owner: "channel",
introduced: "2026-04-29",
docsPath: "/plugins/sdk-channel-plugins",
surfaces: [
"api.registerChannel",
"channel setup metadata",
"channel message envelope"
],
diagnostics: ["channel runtime contract probe"],
tests: ["src/plugin-sdk/channel-entry-contract.test.ts", "src/plugins/captured-registration.test.ts"]
},
{
code: "whatsapp-web-inbound-flat-message-aliases",
status: "removed",
owner: "channel",
introduced: "2026-05-30",
replacement: "WhatsApp `WebInboundCallbackMessage` nested contexts: `event`, `payload`, `quote`, `group`, and `platform`",
docsPath: "/plugins/compatibility",
surfaces: [
"@openclaw/whatsapp WebInboundMessage flat fields",
"WhatsApp monitorWebInbox onMessage callback",
"WhatsApp monitorWebChannel listenerFactory injected messages"
],
diagnostics: ["plugin compatibility registry and compatibility guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "WhatsApp WebInboundMessage flat fields were removed; callbacks now receive only nested inbound contexts."
},
{
code: "whatsapp-web-inbound-admission-top-level-fields",
status: "removed",
owner: "channel",
introduced: "2026-06-14",
replacement: "WhatsApp `WebInboundMessage.admission` fields: `conversation.id`, `accountId`, `ingress.decision`, and `conversation.kind`",
docsPath: "/plugins/compatibility",
surfaces: [
"@openclaw/whatsapp WebInboundMessage top-level admission fields",
"WhatsApp monitorWebInbox onMessage callback",
"WhatsApp monitorWebChannel listenerFactory injected messages"
],
diagnostics: ["plugin compatibility registry and compatibility guide"],
tests: ["src/plugins/compat/registry.test.ts"],
releaseNote: "WhatsApp WebInboundMessage top-level admission fields were removed; callbacks now read the canonical admission envelope."
},
{
code: "sdk-untrusted-context-identifier-aliases",
status: "removal-pending",
owner: "sdk",
introduced: "2026-07-22",
deprecated: "2026-07-22",
warningStarts: "2026-07-22",
removeAfter: "2026-09-08",
replacement: "`MsgContext.ChannelPromptContext`, `MsgContext.ChannelStructuredContext`, `ChannelStructuredContextEntry`, `SupplementalContextFacts.channelStructuredContext`, and `buildChannelMetadata`; retain the aliases until migration of published plugin readers is verified and explicit breaking-release approval is granted",
docsPath: "/plugins/compatibility",
surfaces: [
"openclaw/plugin-sdk reply-runtime MsgContext.UntrustedContext and UntrustedStructuredContext",
"openclaw/plugin-sdk reply-runtime UntrustedStructuredContextEntry",
"openclaw/plugin-sdk channel-inbound SupplementalContextFacts.untrustedContext",
"openclaw/plugin-sdk security-runtime buildUntrustedChannelMetadata"
],
diagnostics: ["TypeScript deprecated SDK alias annotations"],
tests: ["src/auto-reply/reply/inbound-context.test.ts"],
releaseNote: "Untrusted-named prompt-context SDK identifiers remain wired as deprecated aliases of the channel-named fields while plugins migrate."
},
{
code: "channel-explicit-target-parser",
status: "removed",
owner: "sdk",
introduced: "2026-04-28",
replacement: "`messaging.targetResolver` for target normalization and `messaging.resolveOutboundSessionRoute` for session/thread identity",
docsPath: "/plugins/sdk-migration",
surfaces: [
"ChannelMessagingAdapter.parseExplicitTarget",
"openclaw/plugin-sdk/channel-route ChannelRouteExplicitTarget",
"openclaw/plugin-sdk/channel-route ChannelRouteExplicitTargetParser",
"openclaw/plugin-sdk/channel-route resolveChannelRouteTargetWithParser"
],
diagnostics: ["plugin SDK compatibility warning"],
tests: ["src/channels/plugins/contracts/test-helpers/surface-contract-suite.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "The deprecated channel explicit-target parser was removed; plugins must normalize targets with `messaging.targetResolver` and project session identity with `messaging.resolveOutboundSessionRoute`."
},
{
code: "channel-messaging-targets-subpath",
status: "removed",
owner: "sdk",
introduced: "2026-04-28",
replacement: "`openclaw/plugin-sdk/channel-targets`",
docsPath: "/plugins/sdk-migration",
surfaces: ["openclaw/plugin-sdk/messaging-targets"],
diagnostics: ["plugin SDK compatibility warning"],
tests: ["src/plugins/compat/registry.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts"],
releaseNote: "The deprecated `openclaw/plugin-sdk/messaging-targets` subpath was removed; import target helpers from `openclaw/plugin-sdk/channel-targets`."
},
{
code: "bundled-plugin-allowlist",
status: "active",
owner: "config",
introduced: "2026-04-24",
replacement: "manifest-owned plugin enablement and scoped load plans",
docsPath: "/plugins/architecture",
surfaces: [
"plugins.allow",
"bundled provider startup",
"plugins status"
],
diagnostics: ["plugin status report"],
tests: ["src/plugins/status.test.ts", "src/plugins/config-state.test.ts"]
},
{
code: "bundled-plugin-enablement",
status: "active",
owner: "config",
introduced: "2026-04-24",
replacement: "manifest-owned plugin defaults and scoped load plans",
docsPath: "/plugins/architecture",
surfaces: [
"plugins.entries",
"bundled provider startup",
"plugins status"
],
diagnostics: ["plugin status report"],
tests: ["src/plugins/status.test.ts", "src/plugins/config-state.test.ts"]
},
{
code: "activation-agent-harness-hint",
...ACTIVATION_HINT_METADATA,
replacement: "top-level `cliBackends[]` for CLI aliases and future `agentRuntime` ownership metadata",
surfaces: ["activation.onAgentHarnesses", "activation planner"]
},
{
code: "activation-provider-hint",
...ACTIVATION_HINT_METADATA,
replacement: "`providers[]` manifest ownership",
surfaces: ["activation.onProviders", "activation planner"]
},
{
code: "activation-channel-hint",
...ACTIVATION_HINT_METADATA,
replacement: "`channels[]` manifest ownership",
surfaces: ["activation.onChannels", "activation planner"]
},
{
code: "activation-command-hint",
...ACTIVATION_HINT_METADATA,
replacement: "`commandAliases` or command contribution metadata",
surfaces: ["activation.onCommands", "activation planner"]
},
{
code: "activation-route-hint",
...ACTIVATION_HINT_METADATA,
replacement: "HTTP route contribution metadata",
surfaces: ["activation.onRoutes", "activation planner"]
},
{
code: "activation-config-path-hint",
...ACTIVATION_HINT_METADATA,
introduced: "2026-04-27",
replacement: "manifest contribution ownership for root config surfaces",
surfaces: ["activation.onConfigPaths", "startup plugin selection"],
tests: ["src/plugins/channel-plugin-ids.test.ts"]
},
{
code: "activation-capability-hint",
...ACTIVATION_HINT_METADATA,
replacement: "manifest contribution ownership",
surfaces: ["activation.onCapabilities", "activation planner"]
},
{
code: "agent-harness-sdk-alias",
status: "deprecated",
owner: "agent-runtime",
introduced: "2026-04-24",
deprecated: "2026-04-25",
warningStarts: "2026-04-25",
replacement: "none yet; retain until a harness subpath ships and external migration is proven",
docsPath: "/plugins/sdk-agent-harness",
surfaces: ["openclaw/plugin-sdk/agent-harness", "openclaw/plugin-sdk/agent-harness-runtime"],
diagnostics: ["plugin SDK compatibility warning"],
tests: ["src/plugins/contracts/plugin-sdk-subpaths.test.ts"]
},
{
code: "embedded-pi-agent-sdk-aliases",
status: "removed",
owner: "agent-runtime",
introduced: "2026-05-21",
replacement: "`runEmbeddedAgent` and `EmbeddedAgent*` SDK/runtime names",
docsPath: "/plugins/sdk-runtime",
surfaces: [
"api.runtime.agent.runEmbeddedPiAgent",
"openclaw/extension-api runEmbeddedPiAgent",
"openclaw/plugin-sdk/agent-harness-runtime EmbeddedPi* aliases"
],
diagnostics: ["plugin SDK compatibility registry"],
tests: ["src/plugins/runtime/index.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts"],
releaseNote: "The legacy `runEmbeddedPiAgent` and `EmbeddedPi*` plugin aliases were removed; plugins must use the neutral embedded-agent names."
},
{
code: "plugin-sdk-shipped-channel-setup-exports",
status: "deprecated",
owner: "channel",
introduced: "2026-07-23",
deprecated: "2026-07-23",
warningStarts: "2026-07-23",
replacement: "retain until supported published packages migrate to plugin-owned config schemas plus generic `openclaw/plugin-sdk/channel-config-schema` and `openclaw/plugin-sdk/setup-runtime` primitives",
docsPath: "/plugins/sdk-migration#published-channel-setup-compatibility",
surfaces: [
"openclaw/plugin-sdk/bundled-channel-config-schema SlackConfigSchema",
"openclaw/plugin-sdk/bundled-channel-config-schema DiscordConfigSchema",
"openclaw/plugin-sdk/bundled-channel-config-schema SignalConfigSchema",
"openclaw/plugin-sdk/bundled-channel-config-schema MSTeamsConfigSchema",
"openclaw/plugin-sdk/setup-runtime createLegacyCompatChannelDmPolicy",
"openclaw/plugin-sdk/setup-runtime promptLegacyChannelAllowFromForAccount"
],
diagnostics: ["repository deprecated API usage guard for core and bundled plugins; no external runtime import warning"],
tests: ["src/plugin-sdk/shipped-channel-compat.test.ts", "src/plugins/compat/registry.test.ts"],
releaseNote: "Published OpenClaw channel packages through 2026.7.1 remain loadable while they migrate to plugin-owned config and setup helpers."
},
{
code: "generated-bundled-channel-config-fallback",
status: "active",
owner: "channel",
introduced: "2026-04-24",
replacement: "manifest registry `channelConfigs` metadata",
docsPath: "/plugins/manifest",
surfaces: ["generated bundled channel config metadata", "channel config validation"],
diagnostics: ["channel config metadata fallback"],
tests: ["src/plugins/contracts/config-footprint-guardrails.test.ts"]
},
{
code: "setup-runtime-fallback",
status: "active",
owner: "setup",
introduced: "2026-04-24",
replacement: "`setup.requiresRuntime: false` with complete setup descriptors",
docsPath: "/plugins/manifest/setup-and-auth#setup-reference",
surfaces: ["setup-api runtime fallback", "setup.requiresRuntime omitted"],
diagnostics: ["setup registry runtime diagnostic"],
tests: ["src/plugins/setup-registry.test.ts", "src/plugins/setup-registry.runtime.test.ts"]
}
];
//#endregion
//#region src/plugins/compat/registry.ts
function listPluginCompatRecords() {
return PLUGIN_COMPAT_RECORDS;
}
//#endregion
//#region src/plugins/installed-plugin-index-policy.ts
/** Hashes plugin compat registry state that can affect installed index validity. */
function resolveCompatRegistryVersion() {
return hashJson(listPluginCompatRecords().map((record) => ({
code: record.code,
status: record.status,
deprecated: record.deprecated,
warningStarts: record.warningStarts,
removeAfter: record.removeAfter,
removalGate: record.removalGate,
replacement: record.replacement
})));
}
/** Hashes config policy inputs that can change installed plugin activation. */
function resolveInstalledPluginIndexPolicyHash(config, env, behavior = {}) {
const normalized = normalizePluginsConfig(config?.plugins);
const channelPolicy = {};
const channels = config?.channels;
if (channels && typeof channels === "object" && !Array.isArray(channels)) {
for (const [channelId, value] of Object.entries(channels)) if (value && typeof value === "object" && !Array.isArray(value)) {
const enabled = value.enabled;
if (typeof enabled === "boolean") channelPolicy[channelId] = enabled;
}
}
return hashJson({
plugins: {
enabled: normalized.enabled,
allow: normalized.allow,
deny: normalized.deny,
bundledDiscovery: readBundledDiscoveryModeMemoized(env, behavior) ?? null,
slots: normalized.slots,
entries: Object.fromEntries(Object.entries(normalized.entries).flatMap(([pluginId, entry]) => typeof entry.enabled === "boolean" ? [[pluginId, entry.enabled]] : []).toSorted(([left], [right]) => left.localeCompare(right)))
},
channels: Object.fromEntries(Object.entries(channelPolicy).toSorted(([left], [right]) => left.localeCompare(right)))
});
}
//#endregion
//#region src/plugins/installed-plugin-index-facts.ts
/** Package facts share the existing generation; mutable management inputs stay uncached. */
function getInstalledPluginIndexFacts(index) {
const entries = getPluginCache().metadata.indexFacts;
const existing = entries.get(index);
if (existing) return existing;
if (!isDeeplyFrozenPlainData(index)) return;
const facts = {};
entries.set(index, facts);
return facts;
}
//#endregion
//#region src/plugins/bundled-compat.ts
/** Returns config with selected bundled plugins explicitly enabled when compat rules require it. */
function withBundledPluginEnablementCompat(params) {
if (params.pluginIds.length === 0) return params.config;
const existingEntries = params.config?.plugins?.entries ?? {};
const selectPlugins = params.activation !== "defaults";
const forcePluginsEnabled = selectPlugins && params.config?.plugins?.enabled === false;
const allow = params.config?.plugins?.allow;
const bypassAllowlist = readBundledDiscoveryModeMemoized(params.env, { artifactPreservingReadOnly: params.artifactPreservingReadOnly }) === "compat";
const allowSet = !bypassAllowlist && Array.isArray(allow) && allow.length > 0 ? new Set(allow.map((pluginId) => normalizePluginId(pluginId)).filter(Boolean)) : void 0;
let hasEligiblePlugin = false;
let changed = false;
const nextEntries = { ...existingEntries };
const nextAllow = bypassAllowlist && Array.isArray(allow) ? new Set(allow) : void 0;
for (const pluginId of params.pluginIds) {
if (allowSet && !allowSet.has(pluginId)) continue;
hasEligiblePlugin = true;
const beforeAllowSize = nextAllow?.size;
nextAllow?.add(pluginId);
if (nextAllow && nextAllow.size !== beforeAllowSize) changed = true;
if (!selectPlugins || existingEntries[pluginId] !== void 0) continue;
nextEntries[pluginId] = { enabled: true };
changed = true;
}
if (!changed && (!forcePluginsEnabled || !hasEligiblePlugin)) return params.config;
return {
...params.config,
plugins: {
...params.config?.plugins,
...forcePluginsEnabled ? { enabled: true } : {},
...nextAllow ? { allow: [...nextAllow] } : {},
...selectPlugins ? { entries: nextEntries } : {}
}
};
}
//#endregion
//#region src/plugins/bundled-provider-compat.ts
const BUNDLED_PROVIDER_COMPAT_CONTRACT_KEYS = [
"speechProviders",
"externalAuthProviders",
"embeddingProviders",
"mediaUnderstandingProviders",
"transcriptSourceProviders",
"realtimeVoiceProviders",
"realtimeTranscriptionProviders",
"imageGenerationProviders",
"videoGenerationProviders",
"musicGenerationProviders",
"webFetchProviders",
"webSearchProviders",
"workerProviders",
"storageProviders",
"usageProviders",
"migrationProviders"
];
const BUNDLED_PROVIDER_COMPAT_CONTRACTS = new Set(BUNDLED_PROVIDER_COMPAT_CONTRACT_KEYS);
function isBundledProviderCompatContract(contract) {
return BUNDLED_PROVIDER_COMPAT_CONTRACTS.has(contract);
}
function isBundledProviderCompatPlugin(plugin) {
return plugin.origin === "bundled" && ((plugin.providers?.length ?? 0) > 0 || BUNDLED_PROVIDER_COMPAT_CONTRACT_KEYS.some((contract) => (plugin.contracts?.[contract]?.length ?? 0) > 0));
}
//#endregion
//#region src/plugins/installed-plugin-index-install-records.ts
/** Normalizes durable plugin install records into installed-index metadata and back. */
/** Normalizes raw plugin install records into index-safe install record metadata. */
function normalizeInstallRecordMap(records) {
const normalized = parsePluginInstallRecordMap(records ?? {});
if (!normalized) throw new Error("Invalid plugin install record map");
return normalized;
}
function restoreInstallRecordMap(records) {
const restored = parsePluginInstallRecordMap(records ?? {});
if (!restored) throw new Error("Invalid persisted plugin install record map");
return restored;
}
/** Copies canonical install records out of the installed index. */
function extractPluginInstallRecordsFromInstalledPluginIndex(index) {
const facts = index ? getInstalledPluginIndexFacts(index) : void 0;
if (!facts) return restoreInstallRecordMap(index?.installRecords);
const parsed = facts.installRecords ??= restoreInstallRecordMap(index?.installRecords);
const records = createPluginInstallRecordMap();
for (const [pluginId, record] of Object.entries(parsed)) setPluginInstallRecordMapEntry(records, pluginId, {
...record,
...record.clawhubTrustReasons ? { clawhubTrustReasons: [...record.clawhubTrustReasons] } : {},
...record.acceptedSurface ? { acceptedSurface: structuredClone(record.acceptedSurface) } : {}
});
return records;
}
//#endregion
//#region src/plugins/installed-plugin-index-manifest.ts
/** True when the bundle format permits omitting its manifest file. */
function isOptionalPluginManifestFile(record) {
return record.format === "bundle" && record.bundleFormat === "claude";
}
/** True when a Claude bundle record omits its optional manifest file. */
function hasOptionalMissingPluginManifestFile(record) {
return isOptionalPluginManifestFile(record) && !pluginCacheExistsSync(record.manifestPath);
}
//#endregion
//#region src/plugins/installed-plugin-index-record-builder.ts
/** Builds installed-index records from normalized plugin manifest registry entries. */
function buildStartupInfo(record) {
return {
sidecar: record.activation?.onStartup === true,
memory: hasKind(record.kind, "memory"),
agentHarnesses: normalizeSortedUniqueStringEntries([...record.activation?.onAgentHarnesses ?? [], ...record.cliBackends ?? []]),
configPaths: normalizeSortedUniqueStringEntries(record.activation?.onConfigPaths)
};
}
function buildContributionInfo(record) {
const contracts = Object.fromEntries(Object.entries(record.contracts ?? {}).map(([key, values]) => [key, normalizeSortedUniqueStringEntries(values)]));
return {
channels: normalizeSortedUniqueStringEntries(record.channels),
channelConfigs: normalizeSortedUniqueStringEntries(Object.keys(record.channelConfigs ?? {})),
providers: normalizeSortedUniqueStringEntries(record.providers),
modelCatalogProviders: normalizeSortedUniqueStringEntries([
...Object.keys(record.modelCatalog?.providers ?? {}),
...Object.keys(record.modelCatalog?.aliases ?? {}),
...(record.modelCatalog?.suppressions ?? []).map((entry) => entry.provider)
]),
modelSupportPrefixes: normalizeSortedUniqueStringEntries(record.modelSupport?.modelPrefixes),
modelSupportPatterns: normalizeSortedUniqueStringEntries(record.modelSupport?.modelPatterns),
autoEnableProviderIds: normalizeSortedUniqueStringEntries(record.autoEnableWhenConfiguredProviders),
commandAliases: normalizeSortedUniqueStringEntries(record.commandAliases?.map((alias) => alias.name)),
contracts
};
}
const ACTIVATION_COMPAT_CODES = [
["onProviders", "activation-provider-hint"],
["onAgentHarnesses", "activation-agent-harness-hint"],
["onChannels", "activation-channel-hint"],
["onCommands", "activation-command-hint"],
["onRoutes", "activation-route-hint"],
["onConfigPaths", "activation-config-path-hint"],
["onCapabilities", "activation-capability-hint"]
];
/** Collects compatibility codes implied by a manifest's legacy or activation surfaces. */
function collectPluginManifestCompatCodes(record) {
return ACTIVATION_COMPAT_CODES.filter(([key]) => record.activation?.[key]?.length).map(([, code]) => code).toSorted();
}
function resolvePackageJsonRecord(params) {
const { candidate } = params;
if (!candidate?.packageDir) return;
const file = readPluginCacheFile({
rootDir: candidate.packageDir,
relativePath: "package.json",
rejectHardlinks: params.rejectHardlinks
});
if (!file.ok) return;
const rootDir = candidate.rootDir === candidate.packageDir ? file.rootRealPath : pluginCacheRealpathSync(candidate.rootDir) ?? path.resolve(candidate.rootDir);
if (!isPathInside(rootDir, file.path)) return;
const packageJsonPath = path.join(file.rootRealPath, "package.json");
return {
path: (path.relative(rootDir, packageJsonPath) || "package.json").split(path.sep).join("/"),
hash: file.hash,
fileSignature: file.signature
};
}
function describePackageInstallSource(candidate) {
const install = candidate?.packageManifest?.install;
if (!install) return;
return describePluginInstallSource(install, { expectedPackageName: candidate?.packageName });
}
function normalizePackageChannel(channel) {
const id = normalizeOptionalString(channel?.id);
if (!id) return;
return {
...structuredClone(channel),
id
};
}
function hashManifestlessBundleRecord(record) {
return hashJson({
id: record.id,
name: record.name,
description: record.description,
version: record.version,
format: record.format,
bundleFormat: record.bundleFormat,
bundleCapabilities: record.bundleCapabilities ?? [],
skills: record.skills ?? [],
settingsFiles: record.settingsFiles ?? [],
hooks: record.hooks ?? []
});
}
function readRecordFile(params) {
const rootDir = params.boundaryRoot ?? params.record.rootDir;
const file = readPluginCacheFile({
rootDir,
relativePath: path.relative(rootDir, params.filePath),
rejectHardlinks: params.rejectHardlinks,
...params.required && path.extname(params.filePath) === ".json" ? { maxBytes: 262144 } : {}
});
if (file.ok) return file;
if (params.required) params.diagnostics.push({
level: "warn",
pluginId: params.record.id,
source: params.filePath,
message: `installed plugin index could not hash ${params.filePath}: ${file.failure.reason}`
});
}
function buildInstalledPluginIndexRecords(params) {
const candidateBySource = new Map(params.candidates.map((candidate) => [candidate.source, candidate]));
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
return params.registry.plugins.map((record) => {
const candidate = candidateBySource.get(record.source);
const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
origin: record.origin,
rootDir: record.rootDir,
env: params.env
});
const installOwner = candidate && isPluginCandidateInstallOwnerAmbiguous(candidate) ? void 0 : resolvePluginManifestInstallOwner(record) ?? (candidate ? resolvePluginCandidateInstallOwner(candidate) : void 0);
const installRecord = installOwner ? getPluginInstallRecordMapEntry(params.installRecords, installOwner) : void 0;
const packageInstall = describePackageInstallSource(candidate);
const packageChannel = normalizePackageChannel(record.packageChannel ?? candidate?.packageManifest?.channel);
const manifestless = hasOptionalMissingPluginManifestFile(record);
const manifestFile = manifestless ? void 0 : readRecordFile({
record,
filePath: record.manifestPath,
rejectHardlinks,
required: true,
diagnostics: params.diagnostics
});
const manifestHash = manifestless ? hashManifestlessBundleRecord(record) : manifestFile?.hash ?? "";
const doctorContractArtifact = resolvePluginDoctorContractArtifact(record);
const doctorContractFile = doctorContractArtifact ? readRecordFile({
record,
filePath: doctorContractArtifact.modulePath,
boundaryRoot: doctorContractArtifact.boundaryRoot,
rejectHardlinks,
diagnostics: params.diagnostics,
required: false
}) : void 0;
const packageJson = resolvePackageJsonRecord({
candidate,
rejectHardlinks
});
const enabled = resolveEffectiveEnableState({
id: record.id,
origin: record.origin,
channelIds: record.channels,
config: normalizedConfig,
rootConfig: params.config,
enabledByDefault: isPluginEnabledByDefaultForPlatform(record)
}).enabled;
const indexRecord = {
pluginId: record.id,
manifestPath: record.manifestPath,
manifestHash,
...doctorContractFile ? {
doctorContractHash: doctorContractFile.hash,
doctorContractFile: doctorContractFile.signature
} : {},
...manifestFile ? { manifestFile: manifestFile.signature } : {},
source: record.source,
rootDir: record.rootDir,
origin: record.origin,
enabled,
startup: buildStartupInfo(record),
contributions: buildContributionInfo(record),
compat: collectPluginManifestCompatCodes(record)
};
if (record.format && record.format !== "openclaw") indexRecord.format = record.format;
if (record.bundleFormat) indexRecord.bundleFormat = record.bundleFormat;
if (record.enabledByDefault === true) indexRecord.enabledByDefault = true;
if (record.enabledByDefaultOnPlatforms?.length) indexRecord.enabledByDefaultOnPlatforms = [...record.enabledByDefaultOnPlatforms];
if (record.syntheticAuthRefs?.length) indexRecord.syntheticAuthRefs = [...record.syntheticAuthRefs];
if (record.setupSource) indexRecord.setupSource = record.setupSource;
if (candidate?.packageName) indexRecord.packageName = candidate.packageName;
if (candidate?.packageVersion) indexRecord.packageVersion = candidate.packageVersion;
if (installRecord) indexRecord.installRecordHash = hashJson(installRecord);
if (packageInstall) indexRecord.packageInstall = packageInstall;
if (packageChannel) indexRecord.packageChannel = packageChannel;
if (candidate?.packageManifest?.build) indexRecord.packageBuild = structuredClone(candidate.packageManifest.build);
if (packageJson) indexRecord.packageJson = packageJson;
return recordInstalledPluginIndexInstallOwner(indexRecord, installOwner, candidate ? isPluginCandidateInstallOwnerAmbiguous(candidate) : false);
});
}
//#endregion
//#region src/plugins/installed-plugin-index-types.ts
const INSTALLED_PLUGIN_INDEX_WARNING = "DO NOT EDIT. This file is generated by OpenClaw from plugin manifests, install records, and config policy. Use `openclaw plugins registry --refresh`, `openclaw plugins install/update/uninstall`, or `openclaw plugins enable/disable` instead.";
//#endregion
//#region src/plugins/installed-plugin-index-config-path-scope.ts
/** Compat code marking install records that need config-path activation metadata. */
const CONFIG_PATH_ACTIVATION_COMPAT_CODE = "activation-config-path-hint";
function recordUsesConfigPathActivation(plugin) {
return plugin.compat.includes(CONFIG_PATH_ACTIVATION_COMPAT_CODE);
}
/** True when an index still has config-path activation records missing startup metadata. */
function hasMissingConfigPathActivationMetadata(index) {
return index.plugins.some((plugin) => recordUsesConfigPathActivation(plugin) && plugin.startup.configPaths === void 0);
}
/** True when a record migrated config-path activation startup metadata. */
function hasConfigPathActivationMetadataMigration(params) {
return recordUsesConfigPathActivation(params.previous) && params.previous.startup.configPaths === void 0 && params.current.startup.configPaths !== void 0;
}
//#endregion
//#region src/plugins/installed-plugin-index-invalidation.ts
function diffInstalledPluginIndexInvalidationReasons(previous, current) {
const reasons = /* @__PURE__ */ new Set();
if (previous.version !== current.version) reasons.add("missing");
if (previous.hostContractVersion !== current.hostContractVersion) reasons.add("host-contract-changed");
if (previous.compatRegistryVersion !== current.compatRegistryVersion) reasons.add("compat-registry-changed");
if (previous.migrationVersion !== current.migrationVersion) reasons.add("migration");
if (previous.policyHash !== current.policyHash) reasons.add("policy-changed");
if (hashJson(previous.installRecords ?? {}) !== hashJson(current.installRecords ?? {})) reasons.add("source-changed");
const previousByPluginId = new Map(previous.plugins.map((plugin) => [plugin.pluginId, plugin]));
const currentByPluginId = new Map(current.plugins.map((plugin) => [plugin.pluginId, plugin]));
for (const [pluginId, previousPlugin] of previousByPluginId) {
const currentPlugin = currentByPluginId.get(pluginId);
if (!currentPlugin) {
reasons.add("source-changed");
continue;
}
if (previousPlugin.rootDir !== currentPlugin.rootDir || previousPlugin.manifestPath !== currentPlugin.manifestPath || previousPlugin.source !== currentPlugin.source || previousPlugin.setupSource !== currentPlugin.setupSource || resolveInstalledPluginIndexInstallOwner(previousPlugin) !== resolveInstalledPluginIndexInstallOwner(currentPlugin) || isInstalledPluginIndexInstallOwnerAmbiguous(previousPlugin) !== isInstalledPluginIndexInstallOwnerAmbiguous(currentPlugin) || previousPlugin.installRecordHash !== currentPlugin.installRecordHash) reasons.add("source-changed");
if (previousPlugin.enabled !== currentPlugin.enabled) reasons.add("policy-changed");
if (hasConfigPathActivationMetadataMigration({
previous: previousPlugin,
current: currentPlugin
})) reasons.add("migration");
if (previousPlugin.manifestHash !== currentPlugin.manifestHash || previousPlugin.doctorContractHash !== currentPlugin.doctorContractHash) reasons.add("stale-manifest");
if (previousPlugin.packageVersion !== currentPlugin.packageVersion || previousPlugin.packageJson?.path !== currentPlugin.packageJson?.path || previousPlugin.packageJson?.hash !== currentPlugin.packageJson?.hash) reasons.add("stale-package");
}
for (const [pluginId, plugin] of currentByPluginId) if (!previousByPluginId.has(pluginId) && plugin.enabled) reasons.add("source-changed");
return Array.from(reasons).toSorted((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/plugins/installed-plugin-index.ts
function buildInstalledPluginIndex(params) {
const env = params.env ?? process.env;
const installRecords = normalizeInstallRecordMap(params.installRecords ?? loadInstalledPluginIndexInstallRecordsSync({
env,
...params.stateDir ? { stateDir: params.stateDir } : {},
...params.pluginIndexFilePath ? { filePath: params.pluginIndexFilePath } : {}
}));
const baseDiscovery = params.candidates ? {
candidates: params.candidates,
diagnostics: params.diagnostics ?? []
} : params.discovery ?? discoverOpenClawPlugins({
workspaceDir: params.workspaceDir,
extraPaths: normalizePluginsConfig(params.config?.plugins).loadPaths,
env,
installRecords
});
const discovery = !params.candidates && params.diagnostics?.length ? {
...baseDiscovery,
diagnostics: [...baseDiscovery.diagnostics, ...params.diagnostics]
} : baseDiscovery;
const registry = loadPluginManifestRegistryCore({
registryPath: resolveInstalledPluginIndexStorePath({
env,
stateDir: params.stateDir,
filePath: params.pluginIndexFilePath
}),
config: params.config,
workspaceDir: params.workspaceDir,
env,
candidates: discovery.candidates,
diagnostics: discovery.diagnostics,
installRecords
});
const diagnostics = [...registry.diagnostics ?? []];
const generatedAtMs = (params.now?.() ?? /* @__PURE__ */ new Date()).getTime();
const activationConfig = withBundledPluginEnablementCompat({
config: params.config,
env,
pluginIds: registry.plugins.filter(isBundledProviderCompatPlugin).map((plugin) => plugin.id),
activation: "defaults",
artifactPreservingReadOnly: params.artifactPreservingReadOnly
});
const plugins = buildInstalledPluginIndexRecords({
candidates: discovery.candidates,
registry,
config: activationConfig,
env,
diagnostics,
installRecords
});
return {
index: {
version: 1,
warning: INSTALLED_PLUGIN_INDEX_WARNING,
hostContractVersion: resolveCompatibilityHostVersion(env),
compatRegistryVersion: resolveCompatRegistryVersion(),
migrationVersion: 1,
policyHash: resolveInstalledPluginIndexPolicyHash(params.config, env, { artifactPreservingReadOnly: params.artifactPreservingReadOnly }),
generatedAtMs,
...params.workspaceDir !== void 0 ? { workspaceDir: params.workspaceDir } : {},
...params.refreshReason ? { refreshReason: params.refreshReason } : {},
installRecords,
plugins,
diagnostics
},
discovery: params.candidates ? void 0 : discovery,
manifestRegistry: registry
};
}
function loadInstalledPluginIndex(params = {}) {
return buildInstalledPluginIndex(params).index;
}
function loadInstalledPluginIndexWithDiscovery(params = {}) {
return buildInstalledPluginIndex(params);
}
/** True when a persisted index cannot represent the requested workspace discovery scope. */
function hasInstalledPluginIndexWorkspaceScopeMismatch(index, workspaceDir) {
if (workspaceDir !== void 0) return index.workspaceDir !== workspaceDir;
return index.workspaceDir !== void 0 || index.plugins.some((plugin) => plugin.origin === "workspace");
}
function refreshInstalledPluginIndex(params) {
return buildInstalledPluginIndex({
...params,
refreshReason: params.reason
}).index;
}
function isInstalledPluginEnabled(index, pluginId, config, env) {
const record = index.plugins.find((plugin) => plugin.pluginId === pluginId);
if (!record || !config) return record?.enabled ?? false;
return createInstalledPluginEnabledPredicate([record], config, env)(pluginId);
}
function isInstalledBundledProvider(record) {
return isBundledProviderCompatPlugin({
origin: record.origin,
providers: record.contributions?.providers,
contracts: record.contributions?.contracts
});
}
/** Prepare live policy for one synchronous operation, never across config/root changes. */
function createInstalledPluginEnabledPredicate(plugins, config, env) {
let source;
let bundledSource;
let records;
return (pluginId) => {
if (!records) {
records = /* @__PURE__ */ new Map();
for (const entry of plugins) if (!records.has(entry.pluginId)) records.set(entry.pluginId, entry);
}
const record = records.get(pluginId);
if (!record || !config) return record?.enabled ?? false;
let activationSource;
if (isInstalledBundledProvider(record)) {
if (!bundledSource) {
const bundledConfig = withBundledPluginEnablementCompat({
config,
env,
pluginIds: plugins.filter(isInstalledBundledProvider).map((entry) => entry.pluginId),
activation: "defaults"
});
bundledSource = bundledConfig === config ? source ??= createPluginActivationSource({ config }) : createPluginActivationSource({ config: bundledConfig });
}
activationSource = bundledSource;
} else activationSource = source ??= createPluginActivationSource({ config });
return resolveEffectivePluginActivationState({
id: record.pluginId,
origin: record.origin,
channelIds: record.contributions?.channels,
config: activationSource.plugins,
rootConfig: activationSource.rootConfig,
activationSource,
enabledByDefault: isPluginEnabledByDefaultForPlatform(record)
}).enabled;
};
}
//#endregion
export { getInstalledPluginIndexFacts as _, loadInstalledPluginIndexWithDiscovery as a, hasMissingConfigPathActivationMetadata as c, hasOptionalMissingPluginManifestFile as d, isOptionalPluginManifestFile as f, withBundledPluginEnablementCompat as g, isBundledProviderCompatPlugin as h, loadInstalledPluginIndex as i, INSTALLED_PLUGIN_INDEX_WARNING as l, isBundledProviderCompatContract as m, hasInstalledPluginIndexWorkspaceScopeMismatch as n, refreshInstalledPluginIndex as o, extractPluginInstallRecordsFromInstalledPluginIndex as p, isInstalledPluginEnabled as r, diffInstalledPluginIndexInvalidationReasons as s, createInstalledPluginEnabledPredicate as t, collectPluginManifestCompatCodes as u, resolveCompatRegistryVersion as v, resolveInstalledPluginIndexPolicyHash as y };