diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 4d735f752f7dc8..2fcc4e2a7e41b6 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -145,8 +145,10 @@ releases. | Secret input resolution | `openclaw/plugin-sdk/secret-input-runtime` | | Model/session overrides | `openclaw/plugin-sdk/model-session-runtime` | - Bundled production plugins are scanner-guarded against the broad barrel so - imports stay local to the behavior they need. + Bundled plugins and their tests are scanner-guarded against the broad + barrel so imports and mocks stay local to the behavior they need. The broad + barrel still exists for external compatibility, but new code should not + depend on it. @@ -233,6 +235,7 @@ releases. ```bash grep -r "plugin-sdk/compat" my-plugin/ + grep -r "plugin-sdk/config-runtime" my-plugin/ grep -r "openclaw/extension-api" my-plugin/ ``` diff --git a/docs/plugins/sdk-runtime.md b/docs/plugins/sdk-runtime.md index 3414d3a63e2e3f..20a3ee0650a496 100644 --- a/docs/plugins/sdk-runtime.md +++ b/docs/plugins/sdk-runtime.md @@ -41,13 +41,14 @@ Persist changes with `api.runtime.config.mutateConfigFile(...)` or `api.runtime. The mutation helpers return `afterWrite` plus a typed `followUp` summary so callers can log or test whether they requested a restart. The gateway still owns when that restart actually happens. -`api.runtime.config.loadConfig()` and `api.runtime.config.writeConfigFile(...)` are deprecated compatibility helpers under `runtime-config-load-write`. They warn once at runtime, and bundled plugins must not use them; the config boundary guards fail if production plugin code calls them or imports those helpers from plugin SDK subpaths. +`api.runtime.config.loadConfig()` and `api.runtime.config.writeConfigFile(...)` are deprecated compatibility helpers under `runtime-config-load-write`. They warn once at runtime, and remain available for old external plugins during the migration window. Bundled plugins must not use them; the config boundary guards fail if plugin code calls them or imports those helpers from plugin SDK subpaths. For direct SDK imports, use the focused config subpaths instead of the broad `openclaw/plugin-sdk/config-runtime` compatibility barrel: `config-types` for types, `plugin-config-runtime` for already-loaded config assertions and plugin entry lookup, `runtime-config-snapshot` for current process snapshots, and -`config-mutation` for writes. +`config-mutation` for writes. Bundled plugin tests should mock these focused +subpaths directly instead of mocking the broad compatibility barrel. Internal OpenClaw runtime code has the same direction: load config once at the CLI, gateway, or process boundary, then pass that value through. Successful mutation writes refresh the process runtime snapshot and advance its internal revision; long-lived caches should key off the runtime-owned cache key instead of serializing config locally. Long-lived runtime modules have a zero-tolerance scanner for ambient `loadConfig()` calls; use a passed `cfg`, a request `context.getRuntimeConfig()`, or `getRuntimeConfig()` at an explicit process boundary. diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 02534281d32c28..7ec237b668d519 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -26,9 +26,9 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/session-store-runtime", ); return { ...actual, diff --git a/extensions/browser/src/browser-tool.test.ts b/extensions/browser/src/browser-tool.test.ts index d301493cd47ae6..7164d3174f08a8 100644 --- a/extensions/browser/src/browser-tool.test.ts +++ b/extensions/browser/src/browser-tool.test.ts @@ -136,10 +136,10 @@ const configMocks = vi.hoisted(() => ({ } >(() => ({ browser: {} })), })); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, getRuntimeConfig: configMocks.loadConfig, diff --git a/extensions/browser/src/gateway/browser-request.profile-from-body.test.ts b/extensions/browser/src/gateway/browser-request.profile-from-body.test.ts index 3617575b810d8c..dd43bb917c329e 100644 --- a/extensions/browser/src/gateway/browser-request.profile-from-body.test.ts +++ b/extensions/browser/src/gateway/browser-request.profile-from-body.test.ts @@ -8,10 +8,10 @@ const { loadConfigMock, isNodeCommandAllowedMock, resolveNodeCommandAllowlistMoc }), ); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, loadConfig: loadConfigMock, diff --git a/extensions/discord/src/monitor/acp-bind-here.integration.test.ts b/extensions/discord/src/monitor/acp-bind-here.integration.test.ts index b34b3b7c11aedd..f2fc6eef91799d 100644 --- a/extensions/discord/src/monitor/acp-bind-here.integration.test.ts +++ b/extensions/discord/src/monitor/acp-bind-here.integration.test.ts @@ -3,10 +3,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const loadConfigMock = vi.hoisted(() => vi.fn()); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, getRuntimeConfig: () => loadConfigMock(), diff --git a/extensions/discord/src/monitor/provider.startup.test.ts b/extensions/discord/src/monitor/provider.startup.test.ts index 20719e62765b91..e5e440ba23a761 100644 --- a/extensions/discord/src/monitor/provider.startup.test.ts +++ b/extensions/discord/src/monitor/provider.startup.test.ts @@ -24,7 +24,7 @@ vi.mock("@buape/carbon/voice", () => ({ }, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/dangerous-name-runtime", () => ({ isDangerousNameMatchingEnabled: () => false, })); diff --git a/extensions/discord/src/monitor/thread-session-close.test.ts b/extensions/discord/src/monitor/thread-session-close.test.ts index b119ec4b1e4e88..4932ee3398269d 100644 --- a/extensions/discord/src/monitor/thread-session-close.test.ts +++ b/extensions/discord/src/monitor/thread-session-close.test.ts @@ -6,9 +6,9 @@ const hoisted = vi.hoisted(() => { return { updateSessionStore, resolveStorePath }; }); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/session-store-runtime", ); return { ...actual, diff --git a/extensions/discord/src/send.components.test.ts b/extensions/discord/src/send.components.test.ts index 484b4162ce9068..ae12d7af5d9c28 100644 --- a/extensions/discord/src/send.components.test.ts +++ b/extensions/discord/src/send.components.test.ts @@ -15,9 +15,9 @@ const DISCORD_TEST_CFG = { session: { dmScope: "main" }, } as const; -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/discord/src/send.webhook-activity.test.ts b/extensions/discord/src/send.webhook-activity.test.ts index 8d0d7bb5d0f15b..01451fc25244d4 100644 --- a/extensions/discord/src/send.webhook-activity.test.ts +++ b/extensions/discord/src/send.webhook-activity.test.ts @@ -3,13 +3,13 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite const recordChannelActivityMock = vi.hoisted(() => vi.fn()); const loadConfigMock = vi.hoisted(() => vi.fn(() => ({ channels: { discord: {} } }))); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, - loadConfig: () => loadConfigMock(), + requireRuntimeConfig: (cfg: unknown) => cfg ?? loadConfigMock(), }; }); diff --git a/extensions/discord/src/test-support/provider.test-support.ts b/extensions/discord/src/test-support/provider.test-support.ts index 9a2dd4a4eab466..d44162b318258c 100644 --- a/extensions/discord/src/test-support/provider.test-support.ts +++ b/extensions/discord/src/test-support/provider.test-support.ts @@ -379,19 +379,28 @@ vi.mock("openclaw/plugin-sdk/reply-runtime", async () => { }; }); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/native-command-config-runtime", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/native-command-config-runtime") + >("openclaw/plugin-sdk/native-command-config-runtime"); return { ...actual, isNativeCommandsExplicitlyDisabled: () => false, - loadConfig: () => ({}), resolveNativeCommandsEnabled: resolveNativeCommandsEnabledMock, resolveNativeSkillsEnabled: resolveNativeSkillsEnabledMock, }; }); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); + return { + ...actual, + getRuntimeConfig: () => ({}), + }; +}); + vi.mock("openclaw/plugin-sdk/runtime-env", async () => { const actual = await vi.importActual( "openclaw/plugin-sdk/runtime-env", diff --git a/extensions/feishu/src/send.test.ts b/extensions/feishu/src/send.test.ts index 87bb596bdb82fc..c6d3940bb1d9b4 100644 --- a/extensions/feishu/src/send.test.ts +++ b/extensions/feishu/src/send.test.ts @@ -24,7 +24,7 @@ const { mockRuntimeResolveMarkdownTableMode: vi.fn(() => "preserve"), })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/markdown-table-runtime", () => ({ resolveMarkdownTableMode: mockResolveMarkdownTableMode, })); diff --git a/extensions/github-copilot/auth.test.ts b/extensions/github-copilot/auth.test.ts index 74dd3d759e861b..9e83b431b6f235 100644 --- a/extensions/github-copilot/auth.test.ts +++ b/extensions/github-copilot/auth.test.ts @@ -11,7 +11,7 @@ vi.mock("openclaw/plugin-sdk/provider-auth", () => ({ listProfilesForProvider: listProfilesForProviderMock, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/secret-input-runtime", () => ({ resolveRequiredConfiguredSecretRefInputString: resolveRequiredConfiguredSecretRefInputStringMock, })); diff --git a/extensions/github-copilot/embeddings.test.ts b/extensions/github-copilot/embeddings.test.ts index 229b5673680b7c..df6564574a5c2d 100644 --- a/extensions/github-copilot/embeddings.test.ts +++ b/extensions/github-copilot/embeddings.test.ts @@ -9,7 +9,7 @@ vi.mock("./auth.js", () => ({ resolveFirstGithubToken: resolveFirstGithubTokenMock, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/secret-input-runtime", () => ({ resolveConfiguredSecretInputString: resolveConfiguredSecretInputStringMock, })); diff --git a/extensions/irc/src/send.test.ts b/extensions/irc/src/send.test.ts index d5b926abd9643e..25a323fbe228f1 100644 --- a/extensions/irc/src/send.test.ts +++ b/extensions/irc/src/send.test.ts @@ -40,8 +40,8 @@ vi.mock("./protocol.js", async () => { }; }); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const original = (await vi.importActual("openclaw/plugin-sdk/config-runtime")) as Record< +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const original = (await vi.importActual("openclaw/plugin-sdk/plugin-config-runtime")) as Record< string, unknown >; diff --git a/extensions/line/src/bot-handlers.test.ts b/extensions/line/src/bot-handlers.test.ts index 71a0221f537bcd..15d65e2e83e04b 100644 --- a/extensions/line/src/bot-handlers.test.ts +++ b/extensions/line/src/bot-handlers.test.ts @@ -103,7 +103,7 @@ vi.mock("openclaw/plugin-sdk/command-auth", () => ({ hasControlCommand && authorizers.some((entry) => entry.allowed || !entry.configured), }), })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/runtime-group-policy", () => ({ resolveAllowlistProviderRuntimeGroupPolicy: ({ groupPolicy, defaultGroupPolicy, diff --git a/extensions/line/src/send.test.ts b/extensions/line/src/send.test.ts index 5acc642deec7ef..dda51189844a20 100644 --- a/extensions/line/src/send.test.ts +++ b/extensions/line/src/send.test.ts @@ -50,7 +50,7 @@ vi.mock("@line/bot-sdk", () => ({ messagingApi: { MessagingApiClient: MessagingApiClientMock }, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", () => ({ requireRuntimeConfig: requireRuntimeConfigMock, })); diff --git a/extensions/matrix/src/matrix/actions/verification.test.ts b/extensions/matrix/src/matrix/actions/verification.test.ts index bd15c9f43e4dae..d7b33a9de98123 100644 --- a/extensions/matrix/src/matrix/actions/verification.test.ts +++ b/extensions/matrix/src/matrix/actions/verification.test.ts @@ -16,9 +16,9 @@ vi.mock("../../runtime.js", () => ({ }), })); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/matrix/src/matrix/client-resolver.test-helpers.ts b/extensions/matrix/src/matrix/client-resolver.test-helpers.ts index 3721f03e52d0bb..29862f337a65f4 100644 --- a/extensions/matrix/src/matrix/client-resolver.test-helpers.ts +++ b/extensions/matrix/src/matrix/client-resolver.test-helpers.ts @@ -23,9 +23,9 @@ export const matrixClientResolverMocks: MatrixClientResolverMocks = { resolveMatrixAuthContextMock: vi.fn(), }; -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/matrix/src/matrix/send.test.ts b/extensions/matrix/src/matrix/send.test.ts index b8f7c64013b143..dd206927e8e89b 100644 --- a/extensions/matrix/src/matrix/send.test.ts +++ b/extensions/matrix/src/matrix/send.test.ts @@ -33,9 +33,9 @@ const resolveMarkdownTableModeMock = vi.fn(() => "code"); const convertMarkdownTablesMock = vi.fn((text: string) => text); const chunkMarkdownTextWithModeMock = vi.fn((text: string) => (text ? [text] : [])); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/mattermost/src/mattermost/send.test.ts b/extensions/mattermost/src/mattermost/send.test.ts index 18175d11e9cd59..93539082fe1ccf 100644 --- a/extensions/mattermost/src/mattermost/send.test.ts +++ b/extensions/mattermost/src/mattermost/send.test.ts @@ -42,7 +42,7 @@ vi.mock("./runtime-api.js", () => ({ loadOutboundMediaFromUrl: mockState.loadOutboundMediaFromUrl, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", () => ({ requireRuntimeConfig: (cfg: unknown) => { if (cfg) { return cfg; diff --git a/extensions/memory-core/src/dreaming-narrative.test.ts b/extensions/memory-core/src/dreaming-narrative.test.ts index 47cf87aa8def8b..6bdfc70645c6fb 100644 --- a/extensions/memory-core/src/dreaming-narrative.test.ts +++ b/extensions/memory-core/src/dreaming-narrative.test.ts @@ -1,12 +1,13 @@ import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; -import * as configRuntimeModule from "openclaw/plugin-sdk/config-runtime"; import { RequestScopedSubagentRuntimeError, SUBAGENT_RUNTIME_REQUEST_SCOPE_ERROR_CODE, } from "openclaw/plugin-sdk/error-runtime"; import * as memoryCoreHostRuntimeCoreModule from "openclaw/plugin-sdk/memory-core-host-runtime-core"; +import * as runtimeConfigSnapshotModule from "openclaw/plugin-sdk/runtime-config-snapshot"; +import * as sessionStoreRuntimeModule from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, describe, expect, it, vi } from "vitest"; import { resolveGlobalMap } from "../../../src/shared/global-singleton.js"; import { @@ -827,14 +828,16 @@ describe("generateAndAppendDreamNarrative", () => { await fs.utimes(orphanPath, oldDate, oldDate); await fs.utimes(livePath, oldDate, oldDate); - vi.spyOn(configRuntimeModule, "loadConfig").mockReturnValue({ session: {} } as never); - vi.spyOn(configRuntimeModule, "resolveStorePath").mockImplementation((( + vi.spyOn(runtimeConfigSnapshotModule, "getRuntimeConfig").mockReturnValue({ + session: {}, + } as never); + vi.spyOn(sessionStoreRuntimeModule, "resolveStorePath").mockImplementation((( _store: string | undefined, { agentId }: { agentId: string }, ) => { expect(agentId).toBe("main"); return storePath; - }) as typeof configRuntimeModule.resolveStorePath); + }) as typeof sessionStoreRuntimeModule.resolveStorePath); vi.spyOn(memoryCoreHostRuntimeCoreModule, "resolveStateDir").mockReturnValue(stateDir); const subagent = createMockSubagent("The repository whispered of forgotten endpoints."); diff --git a/extensions/msteams/src/send.test.ts b/extensions/msteams/src/send.test.ts index c0b37dfafd1573..5e2a5b56a90ed9 100644 --- a/extensions/msteams/src/send.test.ts +++ b/extensions/msteams/src/send.test.ts @@ -23,7 +23,7 @@ vi.mock("openclaw/plugin-sdk/outbound-media", () => ({ loadOutboundMediaFromUrl: mockState.loadOutboundMediaFromUrl, })); -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/markdown-table-runtime", () => ({ resolveMarkdownTableMode: mockState.resolveMarkdownTableMode, })); diff --git a/extensions/signal/src/monitor.tool-result.test-harness.ts b/extensions/signal/src/monitor.tool-result.test-harness.ts index 8608c9b372f488..a1eae592c74809 100644 --- a/extensions/signal/src/monitor.tool-result.test-harness.ts +++ b/extensions/signal/src/monitor.tool-result.test-harness.ts @@ -98,13 +98,22 @@ export function createMockSignalDaemonHandle( // Use importActual so shared-worker mocks from earlier test files do not leak // into this harness's partial overrides. -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); + return { + ...actual, + getRuntimeConfig: () => config, + }; +}); + +vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/session-store-runtime", ); return { ...actual, - loadConfig: () => config, resolveStorePath: vi.fn(() => signalToolResultSessionStorePath), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), readSessionUpdatedAt: vi.fn(() => undefined), diff --git a/extensions/signal/src/send-reactions.test.ts b/extensions/signal/src/send-reactions.test.ts index d1cf292a7314c5..bf94bba434a0a8 100644 --- a/extensions/signal/src/send-reactions.test.ts +++ b/extensions/signal/src/send-reactions.test.ts @@ -2,9 +2,9 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const rpcMock = vi.fn(); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/slack/src/blocks.test-helpers.ts b/extensions/slack/src/blocks.test-helpers.ts index b2028a2b04bd3f..679c72d8d2c21c 100644 --- a/extensions/slack/src/blocks.test-helpers.ts +++ b/extensions/slack/src/blocks.test-helpers.ts @@ -26,16 +26,6 @@ const slackBlockTestState = vi.hoisted(() => ({ config: {}, })); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); - return { - ...actual, - loadConfig: () => slackBlockTestState.config, - }; -}); - vi.mock("./accounts.js", async () => { const actual = await vi.importActual("./accounts.js"); return { diff --git a/extensions/slack/src/monitor/message-handler/prepare.test.ts b/extensions/slack/src/monitor/message-handler/prepare.test.ts index c832e8ea8f96d7..dce6ff36d55704 100644 --- a/extensions/slack/src/monitor/message-handler/prepare.test.ts +++ b/extensions/slack/src/monitor/message-handler/prepare.test.ts @@ -1591,7 +1591,7 @@ describe("slack thread.requireExplicitMention", () => { const ctx = createCtxWithExplicitMention(true); const { storePath } = storeFixture.makeTmpStorePath(); vi.spyOn( - await import("openclaw/plugin-sdk/config-runtime"), + await import("openclaw/plugin-sdk/session-store-runtime"), "resolveStorePath", ).mockReturnValue(storePath); const account = createSlackTestAccount(); @@ -1618,7 +1618,7 @@ describe("slack thread.requireExplicitMention", () => { const ctx = createCtxWithExplicitMention(true); const { storePath } = storeFixture.makeTmpStorePath(); vi.spyOn( - await import("openclaw/plugin-sdk/config-runtime"), + await import("openclaw/plugin-sdk/session-store-runtime"), "resolveStorePath", ).mockReturnValue(storePath); const account = createSlackTestAccount(); @@ -1645,7 +1645,7 @@ describe("slack thread.requireExplicitMention", () => { const ctx = createCtxWithExplicitMention(false); const { storePath } = storeFixture.makeTmpStorePath(); vi.spyOn( - await import("openclaw/plugin-sdk/config-runtime"), + await import("openclaw/plugin-sdk/session-store-runtime"), "resolveStorePath", ).mockReturnValue(storePath); const account = createSlackTestAccount(); diff --git a/extensions/telegram/src/bot-message-context.dm-threads.test.ts b/extensions/telegram/src/bot-message-context.dm-threads.test.ts index 41bdbd89f2e661..0c8cfad992f94e 100644 --- a/extensions/telegram/src/bot-message-context.dm-threads.test.ts +++ b/extensions/telegram/src/bot-message-context.dm-threads.test.ts @@ -43,7 +43,7 @@ vi.mock("./bot-message-context.body.js", () => ({ const { buildTelegramMessageContextForTest } = await import("./bot-message-context.test-harness.js"); const { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } = - await import("openclaw/plugin-sdk/config-runtime"); + await import("openclaw/plugin-sdk/runtime-config-snapshot"); beforeEach(() => { clearRuntimeConfigSnapshot(); diff --git a/extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts b/extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts index 5a284b0f81d597..7e25be0d95f80b 100644 --- a/extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts +++ b/extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts @@ -20,7 +20,7 @@ vi.mock("./bot-message-context.body.js", () => ({ })); let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest; -let clearRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/config-runtime").clearRuntimeConfigSnapshot; +let clearRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/runtime-config-snapshot").clearRuntimeConfigSnapshot; describe("buildTelegramMessageContext DM topic threadId in deliveryContext (#8891)", () => { async function buildCtx(params: { diff --git a/extensions/telegram/src/bot-message-context.named-account-dm.test-support.ts b/extensions/telegram/src/bot-message-context.named-account-dm.test-support.ts index c36b60f2ea64f0..809c4f65332b87 100644 --- a/extensions/telegram/src/bot-message-context.named-account-dm.test-support.ts +++ b/extensions/telegram/src/bot-message-context.named-account-dm.test-support.ts @@ -6,8 +6,8 @@ import { } from "./bot-message-context.route-test-support.js"; let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest; -let clearRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/config-runtime").clearRuntimeConfigSnapshot; -let setRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/config-runtime").setRuntimeConfigSnapshot; +let clearRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/runtime-config-snapshot").clearRuntimeConfigSnapshot; +let setRuntimeConfigSnapshot: typeof import("openclaw/plugin-sdk/runtime-config-snapshot").setRuntimeConfigSnapshot; describe("buildTelegramMessageContext named-account DM fallback", () => { const baseCfg = { diff --git a/extensions/telegram/src/bot-message-context.topic-agentid.test.ts b/extensions/telegram/src/bot-message-context.topic-agentid.test.ts index cf6815a9087e3b..93cae5ee7de7fe 100644 --- a/extensions/telegram/src/bot-message-context.topic-agentid.test.ts +++ b/extensions/telegram/src/bot-message-context.topic-agentid.test.ts @@ -11,10 +11,10 @@ const { defaultRouteConfig } = vi.hoisted(() => ({ }, })); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, getRuntimeConfig: vi.fn(() => defaultRouteConfig), diff --git a/extensions/telegram/src/bot-native-commands.session-meta.test.ts b/extensions/telegram/src/bot-native-commands.session-meta.test.ts index a83634e4e2f70e..3d4bc91da11ca7 100644 --- a/extensions/telegram/src/bot-native-commands.session-meta.test.ts +++ b/extensions/telegram/src/bot-native-commands.session-meta.test.ts @@ -140,9 +140,9 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => { }), }; }); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/session-store-runtime", ); return { ...actual, diff --git a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts index e5e165d9400d9f..3d1e208ce0e79e 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts @@ -6,9 +6,12 @@ import type { TelegramBotDeps } from "./bot-deps.js"; type AnyMock = ReturnType; type AnyAsyncMock = ReturnType; -type GetRuntimeConfigFn = typeof import("openclaw/plugin-sdk/config-runtime").getRuntimeConfig; -type LoadSessionStoreFn = typeof import("openclaw/plugin-sdk/config-runtime").loadSessionStore; -type ResolveStorePathFn = typeof import("openclaw/plugin-sdk/config-runtime").resolveStorePath; +type GetRuntimeConfigFn = + typeof import("openclaw/plugin-sdk/runtime-config-snapshot").getRuntimeConfig; +type LoadSessionStoreFn = + typeof import("openclaw/plugin-sdk/session-store-runtime").loadSessionStore; +type ResolveStorePathFn = + typeof import("openclaw/plugin-sdk/session-store-runtime").resolveStorePath; type SessionStore = ReturnType; type TelegramBotRuntimeForTest = NonNullable< Parameters[0] diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index 9ab4502721319d..bf766868df4772 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -4,7 +4,8 @@ import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/env import type { TelegramBotOptions } from "./bot.types.js"; const harness = await import("./bot.create-telegram-bot.test-harness.js"); const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime"); -const configRuntime = await import("openclaw/plugin-sdk/config-runtime"); +const configMutation = await import("openclaw/plugin-sdk/config-mutation"); +const sessionStoreRuntime = await import("openclaw/plugin-sdk/session-store-runtime"); const EYES_EMOJI = "\u{1F440}"; const { answerCallbackQuerySpy, @@ -121,7 +122,7 @@ function installPerKeySequentializer(): void { } function mockTelegramConfigWrites() { - return vi.spyOn(configRuntime, "replaceConfigFile").mockResolvedValue({} as never); + return vi.spyOn(configMutation, "replaceConfigFile").mockResolvedValue({} as never); } async function withEnvAsync(env: Record, fn: () => Promise) { @@ -3638,7 +3639,7 @@ describe("createTelegramBot", () => { await dispatch(0); }; - const updateSessionStoreSpy = vi.spyOn(configRuntime, "updateSessionStore"); + const updateSessionStoreSpy = vi.spyOn(sessionStoreRuntime, "updateSessionStore"); updateSessionStoreSpy.mockRejectedValueOnce(new Error("session store boom")); const ctx = { diff --git a/extensions/telegram/src/monitor.test.ts b/extensions/telegram/src/monitor.test.ts index 254c17283f4eb9..3b54481623c206 100644 --- a/extensions/telegram/src/monitor.test.ts +++ b/extensions/telegram/src/monitor.test.ts @@ -263,7 +263,7 @@ async function monitorWithAutoAbort(opts: Omit { +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { return { getRuntimeConfig: getRuntimeConfigMock, resolveAgentMaxConcurrent: (cfg: { agents?: { defaults?: { maxConcurrent?: number } } }) => diff --git a/extensions/telegram/src/send.proxy.test.ts b/extensions/telegram/src/send.proxy.test.ts index e85d2b632c631c..080be87e1e1ad6 100644 --- a/extensions/telegram/src/send.proxy.test.ts +++ b/extensions/telegram/src/send.proxy.test.ts @@ -25,13 +25,13 @@ const resolveTelegramApiBase = vi.hoisted( () => (apiRoot?: string) => apiRoot?.trim()?.replace(/\/+$/, "") || "https://api.telegram.org", ); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, - loadConfig, + requireRuntimeConfig: (cfg: unknown) => cfg ?? loadConfig(), }; }); diff --git a/extensions/telegram/src/send.test-harness.ts b/extensions/telegram/src/send.test-harness.ts index fd8d1e308e169e..c5cdc3967c9e3f 100644 --- a/extensions/telegram/src/send.test-harness.ts +++ b/extensions/telegram/src/send.test-harness.ts @@ -135,9 +135,9 @@ vi.mock("undici", () => ({ setGlobalDispatcher: undiciSetGlobalDispatcher, })); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-config-runtime", ); return { ...actual, diff --git a/extensions/telegram/src/target-writeback.test-shared.ts b/extensions/telegram/src/target-writeback.test-shared.ts index 34d33ed6bc6ca9..2681098f196c70 100644 --- a/extensions/telegram/src/target-writeback.test-shared.ts +++ b/extensions/telegram/src/target-writeback.test-shared.ts @@ -14,15 +14,23 @@ export const loadCronStore: AsyncUnknownMock = vi.fn(); export const resolveCronStorePath: UnknownMock = vi.fn(); export const saveCronStore: AsyncUnknownMock = vi.fn(); -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", +vi.mock("openclaw/plugin-sdk/config-mutation", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/config-mutation", ); return { ...actual, readConfigFileSnapshotForWrite, replaceConfigFile, - writeConfigFile, + }; +}); + +vi.mock("openclaw/plugin-sdk/cron-store-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/cron-store-runtime", + ); + return { + ...actual, loadCronStore, resolveCronStorePath, saveCronStore, diff --git a/extensions/whatsapp/src/active-listener.test.ts b/extensions/whatsapp/src/active-listener.test.ts index 860ad1055e72e1..5720982d93f893 100644 --- a/extensions/whatsapp/src/active-listener.test.ts +++ b/extensions/whatsapp/src/active-listener.test.ts @@ -1,12 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { getActiveWebListener, resolveWebAccountId } from "./active-listener.js"; -vi.mock("openclaw/plugin-sdk/config-runtime", () => ({ - loadConfig: () => ({ - channels: { whatsapp: { accounts: { work: { enabled: true } }, defaultAccount: "work" } }, - }), -})); - const registryMocks = vi.hoisted(() => ({ getRegisteredWhatsAppConnectionController: vi.fn(), })); diff --git a/extensions/whatsapp/src/inbound.media.test.ts b/extensions/whatsapp/src/inbound.media.test.ts index c7f4a72281ee82..eed90225935170 100644 --- a/extensions/whatsapp/src/inbound.media.test.ts +++ b/extensions/whatsapp/src/inbound.media.test.ts @@ -29,13 +29,13 @@ let currentMockSocket: } | undefined; -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, - loadConfig: vi.fn().mockReturnValue({ + getRuntimeConfig: vi.fn().mockReturnValue({ channels: { whatsapp: { allowFrom: ["*"], // Allow all in tests diff --git a/extensions/whatsapp/src/login.coverage.test.ts b/extensions/whatsapp/src/login.coverage.test.ts index 0de40f9ffe5cfe..9c0e1b9b43bda3 100644 --- a/extensions/whatsapp/src/login.coverage.test.ts +++ b/extensions/whatsapp/src/login.coverage.test.ts @@ -13,10 +13,10 @@ function resolveTestAuthDir() { return testState.authDir; } -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, getRuntimeConfig: () => diff --git a/extensions/whatsapp/src/pairing-security.test-harness.ts b/extensions/whatsapp/src/pairing-security.test-harness.ts index 3c3aef83344d91..b45eb670cd62c0 100644 --- a/extensions/whatsapp/src/pairing-security.test-harness.ts +++ b/extensions/whatsapp/src/pairing-security.test-harness.ts @@ -18,13 +18,13 @@ export function resetPairingSecurityMocks(config: Record) { upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true }); } -vi.mock("openclaw/plugin-sdk/config-runtime", async () => { - const actual = await vi.importActual( - "openclaw/plugin-sdk/config-runtime", - ); +vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => { + const actual = await vi.importActual< + typeof import("openclaw/plugin-sdk/runtime-config-snapshot") + >("openclaw/plugin-sdk/runtime-config-snapshot"); return { ...actual, - loadConfig: (...args: unknown[]) => loadConfigMock(...args), + getRuntimeConfig: (...args: unknown[]) => loadConfigMock(...args), }; }); diff --git a/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts index 09c0d829aae5fe..0c10cf4b6c74f1 100644 --- a/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts +++ b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts @@ -7,6 +7,16 @@ import { collectFilesSync, isCodeFile, relativeToCwd } from "./check-file-utils. // imports/exports, require/dynamic import, and test mocks (vi.mock/jest.mock). const ROOT_IMPORT_PATTERN = /["']openclaw\/plugin-sdk["']/; const LEGACY_COMPAT_IMPORT_PATTERN = /["']openclaw\/plugin-sdk\/compat["']/; +const LEGACY_BROAD_SUBPATH_PATTERNS = [ + { + pattern: /["']openclaw\/plugin-sdk\/channel-runtime["']/, + label: "openclaw/plugin-sdk/channel-runtime", + }, + { + pattern: /["']openclaw\/plugin-sdk\/config-runtime["']/, + label: "openclaw/plugin-sdk/config-runtime", + }, +] as const; function hasMonolithicRootImport(content: string): boolean { return ROOT_IMPORT_PATTERN.test(content); @@ -16,6 +26,12 @@ function hasLegacyCompatImport(content: string): boolean { return LEGACY_COMPAT_IMPORT_PATTERN.test(content); } +function findLegacyBroadSubpathImports(content: string): string[] { + return LEGACY_BROAD_SUBPATH_PATTERNS.filter(({ pattern }) => pattern.test(content)).map( + ({ label }) => label, + ); +} + function collectPluginSourceFiles(rootDir: string): string[] { const srcDir = path.join(rootDir, "src"); if (!fs.existsSync(srcDir)) { @@ -71,6 +87,7 @@ function main() { const monolithicOffenders: string[] = []; const legacyCompatOffenders: string[] = []; + const legacyBroadSubpathOffenders = new Map(); for (const entryFile of filesToCheck) { let content = ""; try { @@ -84,9 +101,17 @@ function main() { if (hasLegacyCompatImport(content)) { legacyCompatOffenders.push(entryFile); } + const legacyBroadSubpaths = findLegacyBroadSubpathImports(content); + if (legacyBroadSubpaths.length > 0) { + legacyBroadSubpathOffenders.set(entryFile, legacyBroadSubpaths); + } } - if (monolithicOffenders.length > 0 || legacyCompatOffenders.length > 0) { + if ( + monolithicOffenders.length > 0 || + legacyCompatOffenders.length > 0 || + legacyBroadSubpathOffenders.size > 0 + ) { if (monolithicOffenders.length > 0) { console.error("Bundled plugin source files must not import monolithic openclaw/plugin-sdk."); for (const file of monolithicOffenders.toSorted()) { @@ -101,9 +126,23 @@ function main() { console.error(`- ${relativeToCwd(file)}`); } } - if (monolithicOffenders.length > 0 || legacyCompatOffenders.length > 0) { + if (legacyBroadSubpathOffenders.size > 0) { console.error( - "Use openclaw/plugin-sdk/ or openclaw/plugin-sdk/ subpaths for bundled plugins; root and compat are legacy surfaces only.", + "Bundled plugin source files must not import deprecated broad plugin-sdk subpaths.", + ); + for (const [file, labels] of [...legacyBroadSubpathOffenders.entries()].toSorted( + ([left], [right]) => left.localeCompare(right), + )) { + console.error(`- ${relativeToCwd(file)} (${labels.join(", ")})`); + } + } + if ( + monolithicOffenders.length > 0 || + legacyCompatOffenders.length > 0 || + legacyBroadSubpathOffenders.size > 0 + ) { + console.error( + "Use focused openclaw/plugin-sdk/ subpaths for bundled plugins; root, compat, and broad runtime barrels are legacy surfaces only.", ); } process.exit(1); diff --git a/scripts/lib/config-boundary-guard.mjs b/scripts/lib/config-boundary-guard.mjs index 605881a07b2781..7348b1c03e4bba 100644 --- a/scripts/lib/config-boundary-guard.mjs +++ b/scripts/lib/config-boundary-guard.mjs @@ -32,6 +32,12 @@ const PROCESS_BOUNDARY_DIRECT_CONFIG_LOAD_FILES = new Set([ "src/cli/daemon-cli/status.gather.ts", ]); +const BROAD_CONFIG_RUNTIME_COMPAT_FILES = new Set([ + "scripts/check-no-monolithic-plugin-sdk-entry-imports.ts", + "src/plugins/bundled-capability-runtime.test.ts", + "src/plugins/contracts/config-boundary-guard.test.ts", +]); + function collectTypeScriptFiles(dir) { if (!existsSync(dir)) { return []; @@ -176,6 +182,19 @@ function pushBroadConfigRuntimeBarrelViolations(violations, files) { } } +function pushBroadConfigRuntimeSpecifierViolations(violations, files) { + const moduleSpecifierPattern = /["']openclaw\/plugin-sdk\/config-runtime["']/g; + + for (const { filePath, relPath } of files) { + const source = readFileSync(filePath, "utf8"); + for (const line of findMatchLineNumbers(source, moduleSpecifierPattern)) { + violations.push( + `${relPath}:${line} use narrow plugin-sdk config subpaths instead of openclaw/plugin-sdk/config-runtime`, + ); + } + } +} + export function collectDeprecatedInternalConfigApiViolations({ repoRoot = DEFAULT_REPO_ROOT, } = {}) { @@ -246,6 +265,13 @@ export function collectDeprecatedInternalConfigApiViolations({ !relPath.startsWith("test/"), ), ); + pushBroadConfigRuntimeSpecifierViolations( + violations, + repoFiles.filter( + ({ relPath }) => + !isCompatConfigApiFile(relPath) && !BROAD_CONFIG_RUNTIME_COMPAT_FILES.has(relPath), + ), + ); for (const { filePath, relPath } of repoFiles.filter( ({ relPath }) => !isCompatConfigApiFile(relPath), @@ -354,7 +380,7 @@ export function collectDeprecatedInternalConfigApiViolations({ ); } - return violations; + return [...new Set(violations)]; } const CHANNEL_EXTENSION_IDS = new Set([ diff --git a/src/plugins/contracts/config-boundary-guard.test.ts b/src/plugins/contracts/config-boundary-guard.test.ts index 1e621f3284c319..cee1a23ba5eaa0 100644 --- a/src/plugins/contracts/config-boundary-guard.test.ts +++ b/src/plugins/contracts/config-boundary-guard.test.ts @@ -99,6 +99,19 @@ describe("config boundary guard", () => { ); }); + it("flags broad config-runtime test mocks outside compat guard fixtures", () => { + const repoRoot = makeRepoFixture(); + writeFixture( + repoRoot, + "extensions/telegram/src/index.test.ts", + 'vi.mock("openclaw/plugin-sdk/config-runtime", () => ({}));', + ); + + expect(collectDeprecatedInternalConfigApiViolations({ repoRoot })).toEqual([ + "extensions/telegram/src/index.test.ts:1 use narrow plugin-sdk config subpaths instead of openclaw/plugin-sdk/config-runtime", + ]); + }); + it("allows narrow config SDK subpaths in production code", () => { const repoRoot = makeRepoFixture(); writeFixture(