Skip to content

Commit 55192e2

Browse files
committed
refactor: quarantine bundled plugin inventory
1 parent a9125ec commit 55192e2

6 files changed

Lines changed: 112 additions & 180 deletions

src/agents/model-selection.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, it, expect, vi } from "vitest";
2+
import type { OpenClawConfig } from "../config/config.js";
23
import { resetLogger, setLoggerOverride } from "../logging/logger.js";
34
import {
45
buildAllowedModelSet,

src/plugins/bundled-capability-metadata.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { describe, expect, it } from "vitest";
2+
import { listBundledPluginMetadata } from "./bundled-plugin-metadata.js";
23
import {
34
BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS,
45
BUNDLED_LEGACY_PLUGIN_ID_ALIASES,
56
BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS,
6-
} from "./bundled-capability-metadata.js";
7-
import { listBundledPluginMetadata } from "./bundled-plugin-metadata.js";
7+
} from "./contracts/inventory/bundled-capability-metadata.js";
88

99
function uniqueStrings(values: readonly string[] | undefined): string[] {
1010
const result: string[] = [];

src/plugins/contracts/boundary-invariants.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,21 @@ describe("plugin contract boundary invariants", () => {
2929
return false;
3030
}
3131
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
32-
return source.includes("bundled-capability-metadata");
32+
return source.includes("contracts/inventory/bundled-capability-metadata");
33+
});
34+
expect(offenders).toEqual([]);
35+
});
36+
37+
it("keeps the bundled contract inventory out of non-test runtime code", async () => {
38+
const { globSync } = await import("glob");
39+
const files = globSync("src/**/*.ts", {
40+
cwd: REPO_ROOT,
41+
nodir: true,
42+
ignore: ["src/**/*.test.ts"],
43+
});
44+
const offenders = files.filter((file) => {
45+
const source = readFileSync(resolve(REPO_ROOT, file), "utf8");
46+
return source.includes("contracts/inventory/bundled-capability-metadata");
3347
});
3448
expect(offenders).toEqual([]);
3549
});

src/plugins/bundled-capability-metadata.ts renamed to src/plugins/contracts/inventory/bundled-capability-metadata.ts

Lines changed: 1 addition & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { listBundledPluginMetadata } from "./bundled-plugin-metadata.js";
1+
import { listBundledPluginMetadata } from "../../bundled-plugin-metadata.js";
22

33
// Build/test inventory only.
44
// Runtime code should prefer manifest/runtime registry queries instead of these snapshots.
@@ -67,75 +67,6 @@ export const BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS: readonly BundledPluginContractSn
6767
)
6868
.toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
6969

70-
function collectPluginIds(
71-
pick: (entry: BundledPluginContractSnapshot) => readonly string[],
72-
): readonly string[] {
73-
return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter((entry) => pick(entry).length > 0)
74-
.map((entry) => entry.pluginId)
75-
.toSorted((left, right) => left.localeCompare(right));
76-
}
77-
78-
export const BUNDLED_PROVIDER_PLUGIN_IDS = collectPluginIds((entry) => entry.providerIds);
79-
80-
export const BUNDLED_SPEECH_PLUGIN_IDS = collectPluginIds((entry) => entry.speechProviderIds);
81-
82-
export const BUNDLED_REALTIME_TRANSCRIPTION_PLUGIN_IDS = collectPluginIds(
83-
(entry) => entry.realtimeTranscriptionProviderIds,
84-
);
85-
86-
export const BUNDLED_REALTIME_VOICE_PLUGIN_IDS = collectPluginIds(
87-
(entry) => entry.realtimeVoiceProviderIds,
88-
);
89-
90-
export const BUNDLED_MEDIA_UNDERSTANDING_PLUGIN_IDS = collectPluginIds(
91-
(entry) => entry.mediaUnderstandingProviderIds,
92-
);
93-
94-
export const BUNDLED_IMAGE_GENERATION_PLUGIN_IDS = collectPluginIds(
95-
(entry) => entry.imageGenerationProviderIds,
96-
);
97-
98-
export const BUNDLED_VIDEO_GENERATION_PLUGIN_IDS = collectPluginIds(
99-
(entry) => entry.videoGenerationProviderIds,
100-
);
101-
102-
export const BUNDLED_WEB_FETCH_PLUGIN_IDS = collectPluginIds((entry) => entry.webFetchProviderIds);
103-
104-
export const BUNDLED_RUNTIME_CONTRACT_PLUGIN_IDS = [
105-
...new Set(
106-
BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
107-
(entry) =>
108-
entry.providerIds.length > 0 ||
109-
entry.speechProviderIds.length > 0 ||
110-
entry.realtimeTranscriptionProviderIds.length > 0 ||
111-
entry.realtimeVoiceProviderIds.length > 0 ||
112-
entry.mediaUnderstandingProviderIds.length > 0 ||
113-
entry.imageGenerationProviderIds.length > 0 ||
114-
entry.videoGenerationProviderIds.length > 0 ||
115-
entry.webFetchProviderIds.length > 0 ||
116-
entry.webSearchProviderIds.length > 0,
117-
).map((entry) => entry.pluginId),
118-
),
119-
].toSorted((left, right) => left.localeCompare(right));
120-
121-
export const BUNDLED_WEB_SEARCH_PLUGIN_IDS = collectPluginIds(
122-
(entry) => entry.webSearchProviderIds,
123-
);
124-
125-
export const BUNDLED_WEB_SEARCH_PROVIDER_PLUGIN_IDS = Object.fromEntries(
126-
BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.flatMap((entry) =>
127-
entry.webSearchProviderIds.map((providerId) => [providerId, entry.pluginId] as const),
128-
).toSorted(([left], [right]) => left.localeCompare(right)),
129-
) as Readonly<Record<string, string>>;
130-
131-
export const BUNDLED_PROVIDER_PLUGIN_ID_ALIASES = Object.fromEntries(
132-
BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.flatMap((entry) =>
133-
entry.providerIds
134-
.filter((providerId) => providerId !== entry.pluginId)
135-
.map((providerId) => [providerId, entry.pluginId] as const),
136-
).toSorted(([left], [right]) => left.localeCompare(right)),
137-
) as Readonly<Record<string, string>>;
138-
13970
export const BUNDLED_LEGACY_PLUGIN_ID_ALIASES = Object.fromEntries(
14071
BUNDLED_PLUGIN_METADATA_FOR_CAPABILITIES.flatMap(({ manifest }) =>
14172
(manifest.legacyPluginIds ?? []).map(

src/plugins/contracts/plugin-entry-guardrails.test.ts

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
1-
import { readdirSync, readFileSync } from "node:fs";
2-
import { dirname, resolve } from "node:path";
3-
import { fileURLToPath } from "node:url";
1+
import { readFileSync } from "node:fs";
2+
import { resolve } from "node:path";
43
import { describe, expect, it } from "vitest";
5-
import {
6-
BUNDLED_PLUGIN_ROOT_DIR,
7-
bundledPluginFile,
8-
} from "../../../test/helpers/bundled-plugin-paths.js";
9-
10-
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
11-
const REPO_ROOT = resolve(ROOT_DIR, "..");
12-
const EXTENSIONS_DIR = resolve(REPO_ROOT, BUNDLED_PLUGIN_ROOT_DIR);
4+
import { loadPluginManifestRegistry } from "../manifest-registry.js";
135
const CORE_PLUGIN_ENTRY_IMPORT_RE =
146
/import\s*\{[^}]*\bdefinePluginEntry\b[^}]*\}\s*from\s*"openclaw\/plugin-sdk\/core"/;
157
const RUNTIME_ENTRY_HELPER_RE = /(^|\/)plugin-entry\.runtime\.[cm]?[jt]s$/;
168

9+
function listBundledPluginRoots() {
10+
return loadPluginManifestRegistry({})
11+
.plugins.filter((plugin) => plugin.origin === "bundled")
12+
.map((plugin) => ({
13+
pluginId: plugin.id,
14+
rootDir: plugin.workspaceDir ?? plugin.rootDir,
15+
}))
16+
.toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
17+
}
18+
1719
describe("plugin entry guardrails", () => {
1820
it("keeps bundled extension entry modules off direct definePluginEntry imports from core", () => {
1921
const failures: string[] = [];
2022

21-
for (const entry of readdirSync(EXTENSIONS_DIR, { withFileTypes: true })) {
22-
if (!entry.isDirectory()) {
23-
continue;
24-
}
25-
const indexPath = resolve(EXTENSIONS_DIR, entry.name, "index.ts");
23+
for (const plugin of listBundledPluginRoots()) {
24+
const indexPath = resolve(plugin.rootDir, "index.ts");
2625
try {
2726
const source = readFileSync(indexPath, "utf8");
2827
if (CORE_PLUGIN_ENTRY_IMPORT_RE.test(source)) {
29-
failures.push(bundledPluginFile(entry.name, "index.ts"));
28+
failures.push(`extensions/${plugin.pluginId}/index.ts`);
3029
}
3130
} catch {
3231
// Skip extensions without index.ts entry modules.
@@ -39,11 +38,8 @@ describe("plugin entry guardrails", () => {
3938
it("does not advertise runtime helper sidecars as bundled plugin entry extensions", () => {
4039
const failures: string[] = [];
4140

42-
for (const entry of readdirSync(EXTENSIONS_DIR, { withFileTypes: true })) {
43-
if (!entry.isDirectory()) {
44-
continue;
45-
}
46-
const packageJsonPath = resolve(EXTENSIONS_DIR, entry.name, "package.json");
41+
for (const plugin of listBundledPluginRoots()) {
42+
const packageJsonPath = resolve(plugin.rootDir, "package.json");
4743
try {
4844
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
4945
openclaw?: { extensions?: unknown };
@@ -54,7 +50,7 @@ describe("plugin entry guardrails", () => {
5450
(candidate) => typeof candidate === "string" && RUNTIME_ENTRY_HELPER_RE.test(candidate),
5551
)
5652
) {
57-
failures.push(bundledPluginFile(entry.name, "package.json"));
53+
failures.push(`extensions/${plugin.pluginId}/package.json`);
5854
}
5955
} catch {
6056
// Skip directories without package metadata.

0 commit comments

Comments
 (0)