






















@@ -23,7 +23,10 @@ import {
2323} from "../tasks/detached-task-runtime-state.js";
2424import { withEnv } from "../test-utils/env.js";
2525import type { BundledRuntimeDepsInstallParams } from "./bundled-runtime-deps-install.js";
26-import { resolveBundledRuntimeDependencyInstallRootPlan } from "./bundled-runtime-deps-roots.js";
26+import {
27+resolveBundledRuntimeDependencyInstallRootPlan,
28+resolveBundledRuntimeDependencyPackageInstallRoot,
29+} from "./bundled-runtime-deps-roots.js";
2730import { ensureOpenClawPluginSdkAlias } from "./bundled-runtime-root.js";
2831import { clearPluginCommands } from "./command-registry-state.js";
2932import { getPluginCommandSpecs } from "./command-specs.js";
@@ -95,6 +98,10 @@ import {
9598ensurePluginRegistryLoaded,
9699} from "./runtime/runtime-registry-loader.js";
97100import type { PluginSdkResolutionPreference } from "./sdk-alias.js";
101+import {
102+writeGeneratedRuntimeDepsManifest,
103+writeInstalledRuntimeDepPackage,
104+} from "./test-helpers/bundled-runtime-deps-fixtures.js";
98105let cachedBundledTelegramDir = "";
99106let cachedBundledMemoryDir = "";
100107@@ -118,6 +125,14 @@ function createDetachedTaskRuntimeStub(id: string): DetachedTaskLifecycleRuntime
118125};
119126}
120127128+function realpathOrResolveForTest(value: string): string {
129+try {
130+return fs.realpathSync.native(value);
131+} catch {
132+return path.resolve(value);
133+}
134+}
135+121136const BUNDLED_TELEGRAM_PLUGIN_BODY = `module.exports = {
122137 id: "telegram",
123138 register(api) {
@@ -1592,6 +1607,136 @@ module.exports = {
15921607expect(registry.plugins.find((entry) => entry.id === "alpha")?.status).toBe("loaded");
15931608});
159416091610+it("does not reuse cached bundled runtime deps after an in-place package version upgrade", () => {
1611+const packageRoot = makeTempDir();
1612+const stageDir = makeTempDir();
1613+const markerDir = makeTempDir();
1614+const markerPath = path.join(markerDir, "browser-runtime-marker.json");
1615+const bundledDir = path.join(packageRoot, "dist", "extensions");
1616+const pluginRoot = path.join(bundledDir, "browser");
1617+fs.mkdirSync(pluginRoot, { recursive: true });
1618+fs.writeFileSync(
1619+path.join(pluginRoot, "package.json"),
1620+JSON.stringify(
1621+{
1622+name: "@openclaw/browser",
1623+version: "1.0.0",
1624+dependencies: {
1625+"browser-runtime": "1.0.0",
1626+},
1627+openclaw: { extensions: ["./index.cjs"] },
1628+},
1629+null,
1630+2,
1631+),
1632+"utf-8",
1633+);
1634+fs.writeFileSync(
1635+path.join(pluginRoot, "openclaw.plugin.json"),
1636+JSON.stringify(
1637+{
1638+id: "browser",
1639+enabledByDefault: true,
1640+configSchema: EMPTY_PLUGIN_SCHEMA,
1641+},
1642+null,
1643+2,
1644+),
1645+"utf-8",
1646+);
1647+1648+const env = {
1649+ ...process.env,
1650+OPENCLAW_BUNDLED_PLUGINS_DIR: bundledDir,
1651+OPENCLAW_PLUGIN_STAGE_DIR: stageDir,
1652+OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
1653+VITEST: "true",
1654+};
1655+const writePackageVersion = (version: string) => {
1656+fs.writeFileSync(
1657+path.join(packageRoot, "package.json"),
1658+JSON.stringify({ name: "openclaw", version, type: "module" }, null, 2),
1659+"utf-8",
1660+);
1661+};
1662+const writeRuntimeEntry = (marker: string) => {
1663+fs.writeFileSync(
1664+path.join(pluginRoot, "index.cjs"),
1665+`
1666+const fs = require("node:fs");
1667+const runtimeDep = require("browser-runtime/package.json");
1668+fs.writeFileSync(
1669+ ${JSON.stringify(markerPath)},
1670+ JSON.stringify({ marker: ${JSON.stringify(marker)}, filename: __filename, runtimeDep: runtimeDep.name }) + "\\n",
1671+ "utf-8",
1672+);
1673+module.exports = { id: "browser", register() {} };
1674+`,
1675+"utf-8",
1676+);
1677+};
1678+const installRoots: string[] = [];
1679+const loadOptions = {
1680+ env,
1681+onlyPluginIds: ["browser"],
1682+config: {
1683+plugins: {
1684+enabled: true,
1685+},
1686+},
1687+bundledRuntimeDepsInstaller: ({ installRoot, installSpecs, missingSpecs }) => {
1688+installRoots.push(installRoot);
1689+writeInstalledRuntimeDepPackage(installRoot, "browser-runtime", "1.0.0");
1690+writeGeneratedRuntimeDepsManifest(installRoot, installSpecs ?? missingSpecs);
1691+},
1692+} satisfies Parameters<typeof loadOpenClawPlugins>[0];
1693+1694+writePackageVersion("2026.4.26");
1695+writeRuntimeEntry("v26");
1696+const first = withEnv(env, () => loadOpenClawPlugins(loadOptions));
1697+const firstInstallRoot = resolveBundledRuntimeDependencyPackageInstallRoot(packageRoot, {
1698+ env,
1699+});
1700+const firstPlugin = first.plugins.find((entry) => entry.id === "browser");
1701+expect(firstPlugin?.error).toBeUndefined();
1702+expect(firstPlugin?.status).toBe("loaded");
1703+const firstMarker = JSON.parse(fs.readFileSync(markerPath, "utf-8")) as {
1704+filename: string;
1705+marker: string;
1706+runtimeDep: string;
1707+};
1708+1709+expect(firstMarker.marker).toBe("v26");
1710+expect(firstMarker.runtimeDep).toBe("browser-runtime");
1711+expect(realpathOrResolveForTest(firstMarker.filename)).toContain(
1712+realpathOrResolveForTest(path.join(firstInstallRoot, "dist", "extensions")),
1713+);
1714+expect(installRoots.map((root) => realpathOrResolveForTest(root))).toContain(
1715+realpathOrResolveForTest(firstInstallRoot),
1716+);
1717+1718+writePackageVersion("2026.4.27");
1719+writeRuntimeEntry("v27");
1720+const secondInstallRoot = resolveBundledRuntimeDependencyPackageInstallRoot(packageRoot, {
1721+ env,
1722+});
1723+const second = withEnv(env, () => loadOpenClawPlugins(loadOptions));
1724+const secondMarker = JSON.parse(fs.readFileSync(markerPath, "utf-8")) as {
1725+filename: string;
1726+marker: string;
1727+runtimeDep: string;
1728+};
1729+1730+expect(second).not.toBe(first);
1731+expect(second.plugins.find((entry) => entry.id === "browser")?.status).toBe("loaded");
1732+expect(secondMarker.marker).toBe("v27");
1733+expect(secondMarker.runtimeDep).toBe("browser-runtime");
1734+expect(realpathOrResolveForTest(secondMarker.filename)).toContain(
1735+realpathOrResolveForTest(path.join(secondInstallRoot, "dist", "extensions")),
1736+);
1737+expect(secondInstallRoot).not.toBe(firstInstallRoot);
1738+});
1739+15951740it("loads bundled plugins from symlinked package roots with an external stage dir", () => {
15961741const packageRoot = makeTempDir();
15971742const stageDir = makeTempDir();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。