From 9090457da742159d95306e0b4f5eb686abe103ba Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 15:13:53 +0100 Subject: [PATCH] test(plugin-sdk): use narrow config runtime mocks --- docs/plugins/sdk-migration.md | 7 ++- docs/plugins/sdk-runtime.md | 5 ++- extensions/active-memory/index.test.ts | 6 +-- extensions/browser/src/browser-tool.test.ts | 8 ++-- .../browser-request.profile-from-body.test.ts | 8 ++-- .../monitor/acp-bind-here.integration.test.ts | 8 ++-- .../src/monitor/provider.startup.test.ts | 2 +- .../src/monitor/thread-session-close.test.ts | 6 +-- .../discord/src/send.components.test.ts | 6 +-- .../discord/src/send.webhook-activity.test.ts | 8 ++-- .../src/test-support/provider.test-support.ts | 19 +++++--- extensions/feishu/src/send.test.ts | 2 +- extensions/github-copilot/auth.test.ts | 2 +- extensions/github-copilot/embeddings.test.ts | 2 +- extensions/irc/src/send.test.ts | 4 +- extensions/line/src/bot-handlers.test.ts | 2 +- extensions/line/src/send.test.ts | 2 +- .../src/matrix/actions/verification.test.ts | 6 +-- .../matrix/client-resolver.test-helpers.ts | 6 +-- extensions/matrix/src/matrix/send.test.ts | 6 +-- .../mattermost/src/mattermost/send.test.ts | 2 +- .../src/dreaming-narrative.test.ts | 11 +++-- extensions/msteams/src/send.test.ts | 2 +- .../src/monitor.tool-result.test-harness.ts | 17 +++++-- extensions/signal/src/send-reactions.test.ts | 6 +-- extensions/slack/src/blocks.test-helpers.ts | 10 ----- .../monitor/message-handler/prepare.test.ts | 6 +-- .../bot-message-context.dm-threads.test.ts | 2 +- ...-message-context.dm-topic-threadid.test.ts | 2 +- ...e-context.named-account-dm.test-support.ts | 4 +- .../bot-message-context.topic-agentid.test.ts | 8 ++-- .../bot-native-commands.session-meta.test.ts | 6 +-- .../bot.create-telegram-bot.test-harness.ts | 9 ++-- .../src/bot.create-telegram-bot.test.ts | 7 +-- extensions/telegram/src/monitor.test.ts | 2 +- extensions/telegram/src/send.proxy.test.ts | 8 ++-- extensions/telegram/src/send.test-harness.ts | 6 +-- .../src/target-writeback.test-shared.ts | 16 +++++-- .../whatsapp/src/active-listener.test.ts | 6 --- extensions/whatsapp/src/inbound.media.test.ts | 10 ++--- .../whatsapp/src/login.coverage.test.ts | 8 ++-- .../src/pairing-security.test-harness.ts | 10 ++--- ...-no-monolithic-plugin-sdk-entry-imports.ts | 45 +++++++++++++++++-- scripts/lib/config-boundary-guard.mjs | 28 +++++++++++- .../contracts/config-boundary-guard.test.ts | 13 ++++++ 45 files changed, 229 insertions(+), 130 deletions(-) diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 4d735f752f7..2fcc4e2a7e4 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 3414d3a63e2..20a3ee0650a 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 02534281d32..7ec237b668d 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 d301493cd47..7164d3174f0 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 3617575b810..dd43bb917c3 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 b34b3b7c11a..f2fc6eef917 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 20719e62765..e5e440ba23a 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 b119ec4b1e4..4932ee33982 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 484b4162ce9..ae12d7af5d9 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 8d0d7bb5d0f..01451fc2524 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 9a2dd4a4eab..d44162b3182 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 87bb596bdb8..c6d3940bb1d 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 74dd3d759e8..9e83b431b6f 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 229b5673680..df6564574a5 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 d5b926abd96..25a323fbe22 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 71a0221f537..15d65e2e83e 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 5acc642deec..dda51189844 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 bd15c9f43e4..d7b33a9de98 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 3721f03e52d..29862f337a6 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 b8f7c64013b..dd206927e8e 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 18175d11e9c..93539082fe1 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 47cf87aa8de..6bdfc70645c 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 c0b37dfafd1..5e2a5b56a90 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 8608c9b372f..a1eae592c74 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 d1cf292a731..bf94bba434a 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 b2028a2b04b..679c72d8d2c 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 c832e8ea8f9..dce6ff36d55 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 41bdbd89f2e..0c8cfad992f 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 5a284b0f81d..7e25be0d95f 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 c36b60f2ea6..809c4f65332 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 cf6815a9087..93cae5ee7de 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 a83634e4e2f..3d4bc91da11 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 e5e165d9400..3d1e208ce0e 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 9ab45027213..bf766868df4 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 254c17283f4..3b54481623c 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 e85d2b632c6..080be87e1e1 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 fd8d1e308e1..c5cdc3967c9 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 34d33ed6bc6..2681098f196 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 860ad1055e7..5720982d93f 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 c7f4a72281e..eed90225935 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 0de40f9ffe5..9c0e1b9b43b 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 3c3aef83344..b45eb670cd6 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 09c0d829aae..0c10cf4b6c7 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 605881a07b2..7348b1c03e4 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 1e621f3284c..cee1a23ba5e 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(