
























@@ -14,6 +14,7 @@ import {
1414isWritableDirectory,
1515resolveBundledRuntimeDependencyInstallRoot,
1616resolveBundledRuntimeDepsNpmRunner,
17+scanBundledPluginRuntimeDeps,
1718type BundledRuntimeDepsInstallParams,
1819} from "./bundled-runtime-deps.js";
1920@@ -41,6 +42,30 @@ function writeInstalledPackage(rootDir: string, packageName: string, version: st
4142);
4243}
434445+function writeBundledPluginPackage(params: {
46+packageRoot: string;
47+pluginId: string;
48+deps: Record<string, string>;
49+enabledByDefault?: boolean;
50+channels?: string[];
51+}): string {
52+const pluginRoot = path.join(params.packageRoot, "dist", "extensions", params.pluginId);
53+fs.mkdirSync(pluginRoot, { recursive: true });
54+fs.writeFileSync(
55+path.join(pluginRoot, "package.json"),
56+JSON.stringify({ dependencies: params.deps }),
57+);
58+fs.writeFileSync(
59+path.join(pluginRoot, "openclaw.plugin.json"),
60+JSON.stringify({
61+id: params.pluginId,
62+enabledByDefault: params.enabledByDefault === true,
63+ ...(params.channels ? { channels: params.channels } : {}),
64+}),
65+);
66+return pluginRoot;
67+}
68+4469function statfsFixture(params: {
4570bavail: number;
4671bsize?: number;
@@ -587,6 +612,116 @@ describe("installBundledRuntimeDeps", () => {
587612});
588613});
589614615+describe("scanBundledPluginRuntimeDeps config policy", () => {
616+function setupPolicyPackageRoot(): string {
617+const packageRoot = makeTempDir();
618+writeBundledPluginPackage({
619+ packageRoot,
620+pluginId: "alpha",
621+deps: { "alpha-runtime": "1.0.0" },
622+enabledByDefault: true,
623+});
624+writeBundledPluginPackage({
625+ packageRoot,
626+pluginId: "telegram",
627+deps: { "telegram-runtime": "2.0.0" },
628+channels: ["telegram"],
629+});
630+return packageRoot;
631+}
632+633+it.each([
634+{
635+name: "includes default-enabled bundled plugins",
636+config: {},
637+includeConfiguredChannels: false,
638+expectedDeps: ["alpha-runtime@1.0.0"],
639+},
640+{
641+name: "keeps default-enabled bundled plugins behind restrictive allowlists",
642+config: { plugins: { allow: ["browser"] } },
643+includeConfiguredChannels: false,
644+expectedDeps: [],
645+},
646+{
647+name: "does not let explicit plugin entries bypass restrictive allowlists",
648+config: { plugins: { allow: ["browser"], entries: { alpha: { enabled: true } } } },
649+includeConfiguredChannels: false,
650+expectedDeps: [],
651+},
652+{
653+name: "lets deny override default-enabled bundled plugins",
654+config: { plugins: { deny: ["alpha"] } },
655+includeConfiguredChannels: false,
656+expectedDeps: [],
657+},
658+{
659+name: "lets disabled entries override default-enabled bundled plugins",
660+config: { plugins: { entries: { alpha: { enabled: false } } } },
661+includeConfiguredChannels: false,
662+expectedDeps: [],
663+},
664+{
665+name: "lets explicit bundled channel enablement bypass restrictive allowlists",
666+config: {
667+plugins: { allow: ["browser"] },
668+channels: { telegram: { enabled: true } },
669+},
670+includeConfiguredChannels: false,
671+expectedDeps: ["telegram-runtime@2.0.0"],
672+},
673+{
674+name: "keeps channel recovery behind restrictive allowlists",
675+config: {
676+plugins: { allow: ["browser"] },
677+channels: { telegram: { botToken: "123:abc" } },
678+},
679+includeConfiguredChannels: true,
680+expectedDeps: [],
681+},
682+{
683+name: "includes configured channels during recovery without restrictive allowlists",
684+config: { channels: { telegram: { botToken: "123:abc" } } },
685+includeConfiguredChannels: true,
686+expectedDeps: ["alpha-runtime@1.0.0", "telegram-runtime@2.0.0"],
687+},
688+{
689+name: "lets explicit channel disable override recovery",
690+config: { channels: { telegram: { botToken: "123:abc", enabled: false } } },
691+includeConfiguredChannels: true,
692+expectedDeps: ["alpha-runtime@1.0.0"],
693+},
694+])("$name", ({ config, includeConfiguredChannels, expectedDeps }) => {
695+const result = scanBundledPluginRuntimeDeps({
696+packageRoot: setupPolicyPackageRoot(),
697+ config,
698+ includeConfiguredChannels,
699+});
700+701+expect(result.deps.map((dep) => `${dep.name}@${dep.version}`)).toEqual(expectedDeps);
702+expect(result.conflicts).toEqual([]);
703+});
704+705+it("reads each bundled plugin manifest once per runtime-deps scan", () => {
706+const packageRoot = makeTempDir();
707+const pluginRoot = writeBundledPluginPackage({
708+ packageRoot,
709+pluginId: "alpha",
710+deps: { "alpha-runtime": "1.0.0" },
711+enabledByDefault: true,
712+channels: ["alpha"],
713+});
714+const manifestPath = path.join(pluginRoot, "openclaw.plugin.json");
715+const readFileSyncSpy = vi.spyOn(fs, "readFileSync");
716+717+scanBundledPluginRuntimeDeps({ packageRoot, config: {} });
718+719+expect(
720+readFileSyncSpy.mock.calls.filter((call) => path.resolve(String(call[0])) === manifestPath),
721+).toHaveLength(1);
722+});
723+});
724+590725describe("ensureBundledPluginRuntimeDeps", () => {
591726it("installs plugin-local runtime deps when one is missing", () => {
592727const packageRoot = makeTempDir();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。