mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:10:44 +00:00
test: guard plugin boundary classifications
This commit is contained in:
@@ -273,8 +273,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface |
|
||||
| Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface |
|
||||
| IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface |
|
||||
| Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu-conversation`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/tlon`, `plugin-sdk/twitch` | Deprecated bundled channel compatibility/helper seams. New plugins should import generic SDK subpaths or plugin-local barrels. |
|
||||
| Auth/plugin-specific helpers | `plugin-sdk/github-copilot-login`, `plugin-sdk/github-copilot-token`, `plugin-sdk/diagnostics-otel`, `plugin-sdk/diagnostics-prometheus`, `plugin-sdk/diffs`, `plugin-sdk/llm-task`, `plugin-sdk/thread-ownership`, `plugin-sdk/voice-call` | Bundled feature/plugin helper seams; `plugin-sdk/github-copilot-token` currently exports `DEFAULT_COPILOT_API_BASE_URL`, `deriveCopilotApiBaseUrlFromToken`, and `resolveCopilotApiToken` |
|
||||
| Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/googlechat-runtime-shared`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu`, `plugin-sdk/feishu-conversation`, `plugin-sdk/feishu-setup`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/tlon`, `plugin-sdk/twitch`, `plugin-sdk/zalo`, `plugin-sdk/zalo-setup` | Deprecated bundled channel compatibility/helper seams. New plugins should import generic SDK subpaths or plugin-local barrels. |
|
||||
| Auth/plugin-specific helpers | `plugin-sdk/github-copilot-login`, `plugin-sdk/github-copilot-token`, `plugin-sdk/diagnostics-otel`, `plugin-sdk/diagnostics-prometheus`, `plugin-sdk/diffs`, `plugin-sdk/llm-task`, `plugin-sdk/memory-core`, `plugin-sdk/memory-lancedb`, `plugin-sdk/thread-ownership`, `plugin-sdk/voice-call` | Bundled feature/plugin helper seams; `plugin-sdk/github-copilot-token` currently exports `DEFAULT_COPILOT_API_BASE_URL`, `deriveCopilotApiBaseUrlFromToken`, and `resolveCopilotApiToken` |
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -4,6 +4,66 @@ export const pluginSdkEntrypoints = [...pluginSdkEntryList];
|
||||
|
||||
export const pluginSdkSubpaths = pluginSdkEntrypoints.filter((entry) => entry !== "index");
|
||||
|
||||
export const reservedBundledPluginSdkEntrypoints = [
|
||||
"bluebubbles",
|
||||
"bluebubbles-policy",
|
||||
"browser-cdp",
|
||||
"browser-config-runtime",
|
||||
"browser-config-support",
|
||||
"browser-control-auth",
|
||||
"browser-node-runtime",
|
||||
"browser-profiles",
|
||||
"browser-security-runtime",
|
||||
"browser-setup-tools",
|
||||
"browser-support",
|
||||
"diagnostics-otel",
|
||||
"diagnostics-prometheus",
|
||||
"diffs",
|
||||
"feishu",
|
||||
"feishu-conversation",
|
||||
"feishu-setup",
|
||||
"github-copilot-login",
|
||||
"github-copilot-token",
|
||||
"googlechat",
|
||||
"googlechat-runtime-shared",
|
||||
"irc",
|
||||
"irc-surface",
|
||||
"line",
|
||||
"line-core",
|
||||
"line-runtime",
|
||||
"line-surface",
|
||||
"llm-task",
|
||||
"matrix",
|
||||
"matrix-helper",
|
||||
"matrix-runtime-heavy",
|
||||
"matrix-runtime-shared",
|
||||
"matrix-runtime-surface",
|
||||
"matrix-surface",
|
||||
"matrix-thread-bindings",
|
||||
"mattermost",
|
||||
"mattermost-policy",
|
||||
"memory-core",
|
||||
"memory-lancedb",
|
||||
"msteams",
|
||||
"nextcloud-talk",
|
||||
"nostr",
|
||||
"thread-ownership",
|
||||
"tlon",
|
||||
"twitch",
|
||||
"voice-call",
|
||||
"zalo",
|
||||
"zalo-setup",
|
||||
"zalouser",
|
||||
] as const;
|
||||
|
||||
export const supportedBundledFacadeSdkEntrypoints = [
|
||||
"lmstudio",
|
||||
"lmstudio-runtime",
|
||||
"memory-core-engine-runtime",
|
||||
"qa-runner-runtime",
|
||||
"tts-runtime",
|
||||
] as const;
|
||||
|
||||
/** Map every SDK entrypoint name to its source file path inside the repo. */
|
||||
export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSdkEntrypoints) {
|
||||
return Object.fromEntries(entries.map((entry) => [entry, `src/plugin-sdk/${entry}.ts`]));
|
||||
|
||||
@@ -2,7 +2,11 @@ import { readdirSync, readFileSync } from "node:fs";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { pluginSdkEntrypoints } from "../../plugin-sdk/entrypoints.js";
|
||||
import {
|
||||
pluginSdkEntrypoints,
|
||||
reservedBundledPluginSdkEntrypoints,
|
||||
supportedBundledFacadeSdkEntrypoints,
|
||||
} from "../../plugin-sdk/entrypoints.js";
|
||||
|
||||
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const REPO_ROOT = resolve(ROOT_DIR, "..");
|
||||
@@ -11,6 +15,13 @@ const PUBLIC_CONTRACT_REFERENCE_FILES = [
|
||||
"src/plugins/contracts/plugin-sdk-subpaths.test.ts",
|
||||
] as const;
|
||||
const PLUGIN_SDK_SUBPATH_PATTERN = /openclaw\/plugin-sdk\/([a-z0-9][a-z0-9-]*)\b/g;
|
||||
const BUNDLED_PLUGIN_FACADE_LOADER_PATTERN =
|
||||
/\bload(?:Activated)?BundledPluginPublicSurfaceModuleSync\b/;
|
||||
const PRIVATE_BUNDLED_SDK_SURFACE_PATTERN =
|
||||
/\b(?:Private helper surface|Narrow plugin-sdk surface for the bundled|Narrow .*runtime exports used by the bundled)\b/i;
|
||||
const GENERIC_CORE_HELPER_FILES = ["src/polls.ts", "src/poll-params.ts"] as const;
|
||||
const GENERIC_CORE_PLUGIN_OWNER_NAME_PATTERN =
|
||||
/\b(?:bluebubbles|discord|feishu|googlechat|matrix|mattermost|msteams|slack|telegram|whatsapp|zalo|zalouser)\b/gi;
|
||||
|
||||
function collectPluginSdkPackageExports(): string[] {
|
||||
const packageJson = JSON.parse(readFileSync(resolve(REPO_ROOT, "package.json"), "utf8")) as {
|
||||
@@ -46,6 +57,45 @@ function collectPluginSdkSubpathReferences() {
|
||||
return references;
|
||||
}
|
||||
|
||||
function collectBundledFacadeSdkEntrypoints(): string[] {
|
||||
const entrypoints: string[] = [];
|
||||
for (const entrypoint of pluginSdkEntrypoints) {
|
||||
const filePath = resolve(REPO_ROOT, "src/plugin-sdk", `${entrypoint}.ts`);
|
||||
const source = readFileSync(filePath, "utf8");
|
||||
if (BUNDLED_PLUGIN_FACADE_LOADER_PATTERN.test(source)) {
|
||||
entrypoints.push(entrypoint);
|
||||
}
|
||||
}
|
||||
return entrypoints.toSorted();
|
||||
}
|
||||
|
||||
function collectPrivateBundledSdkSurfaceEntrypoints(): string[] {
|
||||
const entrypoints: string[] = [];
|
||||
for (const entrypoint of pluginSdkEntrypoints) {
|
||||
const filePath = resolve(REPO_ROOT, "src/plugin-sdk", `${entrypoint}.ts`);
|
||||
const source = readFileSync(filePath, "utf8");
|
||||
if (PRIVATE_BUNDLED_SDK_SURFACE_PATTERN.test(source)) {
|
||||
entrypoints.push(entrypoint);
|
||||
}
|
||||
}
|
||||
return entrypoints.toSorted();
|
||||
}
|
||||
|
||||
function collectGenericCoreOwnerNameLeaks(): Array<{ file: string; match: string }> {
|
||||
const leaks: Array<{ file: string; match: string }> = [];
|
||||
for (const file of GENERIC_CORE_HELPER_FILES) {
|
||||
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
|
||||
for (const match of source.matchAll(GENERIC_CORE_PLUGIN_OWNER_NAME_PATTERN)) {
|
||||
const ownerName = match[0];
|
||||
if (!ownerName) {
|
||||
continue;
|
||||
}
|
||||
leaks.push({ file, match: ownerName });
|
||||
}
|
||||
}
|
||||
return leaks;
|
||||
}
|
||||
|
||||
function readRootPackageJson(): {
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
@@ -148,6 +198,32 @@ describe("plugin-sdk package contract guardrails", () => {
|
||||
expect(collectPluginSdkPackageExports()).toEqual([...pluginSdkEntrypoints].toSorted());
|
||||
});
|
||||
|
||||
it("keeps bundled plugin SDK compatibility subpaths explicitly classified", () => {
|
||||
const entrypoints = new Set(pluginSdkEntrypoints);
|
||||
const reserved = new Set<string>(reservedBundledPluginSdkEntrypoints);
|
||||
const supported = new Set<string>(supportedBundledFacadeSdkEntrypoints);
|
||||
const unknownReserved = [...reserved].filter((entrypoint) => !entrypoints.has(entrypoint));
|
||||
const unknownSupported = [...supported].filter((entrypoint) => !entrypoints.has(entrypoint));
|
||||
const unclassifiedBundledFacades = collectBundledFacadeSdkEntrypoints().filter(
|
||||
(entrypoint) => !reserved.has(entrypoint) && !supported.has(entrypoint),
|
||||
);
|
||||
const unreservedPrivateSurfaces = collectPrivateBundledSdkSurfaceEntrypoints().filter(
|
||||
(entrypoint) => !reserved.has(entrypoint),
|
||||
);
|
||||
|
||||
expect({
|
||||
unknownReserved,
|
||||
unknownSupported,
|
||||
unclassifiedBundledFacades,
|
||||
unreservedPrivateSurfaces,
|
||||
}).toEqual({
|
||||
unknownReserved: [],
|
||||
unknownSupported: [],
|
||||
unclassifiedBundledFacades: [],
|
||||
unreservedPrivateSurfaces: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps curated public plugin-sdk references on exported built subpaths", () => {
|
||||
const entrypoints = new Set(pluginSdkEntrypoints);
|
||||
const exports = new Set(collectPluginSdkPackageExports());
|
||||
@@ -192,4 +268,8 @@ describe("plugin-sdk package contract guardrails", () => {
|
||||
it("keeps extension sources on public sdk or local package seams", () => {
|
||||
expect(collectExtensionCoreImportLeaks()).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps generic core poll helpers free of plugin owner names", () => {
|
||||
expect(collectGenericCoreOwnerNameLeaks()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ export type PollInput = {
|
||||
maxSelections?: number;
|
||||
/**
|
||||
* Poll duration in seconds.
|
||||
* Channel-specific limits apply (e.g. Telegram open_period is 5-600s).
|
||||
* Channel-specific limits apply in each owning plugin.
|
||||
*/
|
||||
durationSeconds?: number;
|
||||
/**
|
||||
* Poll duration in hours.
|
||||
* Used by channels that model duration in hours (e.g. Discord).
|
||||
* Used by channels that model duration in hours.
|
||||
*/
|
||||
durationHours?: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user