mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(plugins): repair incomplete runtime-deps mirrors
This commit is contained in:
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/runtime-deps: verify staged package entry files before reusing mirrored runtime roots, so browser-control repairs incomplete `ajv`/MCP SDK installs after update instead of failing after restart on a missing `ajv/dist/ajv.js`. Refs #74630. Thanks @spickeringlr.
|
||||
- Channels/Feishu: retry file-typed iOS video resource downloads as `media` after a Feishu/Lark HTTP 502 and preserve the original 502 when the fallback also fails. Fixes #49855; carries forward #50164 and #73986. Thanks @alex-xuweilong.
|
||||
- Providers/Amazon Bedrock: expose the full Claude Opus 4.7 thinking profile (`xhigh`, `adaptive`, and `max`) for Bedrock model refs, while keeping Opus/Sonnet 4.6 on adaptive-by-default, so `/think` menus and validation match the Anthropic transport behavior. Fixes #74701. Thanks @prasad-yashdeep, @sparkleHazard, @Sanjays2402, and @hclsys.
|
||||
- Plugins/tokenjuice: compile the bundled plugin against tokenjuice 0.7.0's published OpenClaw host types instead of a local compatibility shim, so package contract drift fails in OpenClaw validation before release. Thanks @vincentkoc.
|
||||
|
||||
@@ -52,27 +52,49 @@ function sameRuntimeDepSpecs(left: readonly string[], right: readonly string[]):
|
||||
);
|
||||
}
|
||||
|
||||
function readInstalledRuntimeDepVersion(rootDir: string, depName: string): string | null {
|
||||
function readInstalledRuntimeDepPackage(
|
||||
rootDir: string,
|
||||
depName: string,
|
||||
): { packageDir: string; packageJson: JsonObject } | null {
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
fs.readFileSync(resolveDependencySentinelAbsolutePath(rootDir, depName), "utf8"),
|
||||
) as unknown;
|
||||
const packageJsonPath = resolveDependencySentinelAbsolutePath(rootDir, depName);
|
||||
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as unknown;
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return null;
|
||||
}
|
||||
const version = (parsed as JsonObject).version;
|
||||
return typeof version === "string" && version.trim() ? version.trim() : null;
|
||||
return { packageDir: path.dirname(packageJsonPath), packageJson: parsed as JsonObject };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function hasInstalledRuntimeDepEntryFiles(packageDir: string, packageJson: JsonObject): boolean {
|
||||
const main = packageJson.main;
|
||||
if (typeof main !== "string" || main.trim() === "") {
|
||||
return true;
|
||||
}
|
||||
const mainPath = path.resolve(packageDir, main);
|
||||
if (mainPath !== packageDir && !mainPath.startsWith(`${packageDir}${path.sep}`)) {
|
||||
return false;
|
||||
}
|
||||
return fs.existsSync(mainPath);
|
||||
}
|
||||
|
||||
export function isRuntimeDepSatisfied(
|
||||
rootDir: string,
|
||||
dep: { name: string; version: string },
|
||||
): boolean {
|
||||
const installedVersion = readInstalledRuntimeDepVersion(rootDir, dep.name);
|
||||
return Boolean(installedVersion && satisfies(installedVersion, dep.version));
|
||||
const installed = readInstalledRuntimeDepPackage(rootDir, dep.name);
|
||||
if (!installed) {
|
||||
return false;
|
||||
}
|
||||
const version = installed.packageJson.version;
|
||||
return Boolean(
|
||||
typeof version === "string" &&
|
||||
version.trim() &&
|
||||
satisfies(version.trim(), dep.version) &&
|
||||
hasInstalledRuntimeDepEntryFiles(installed.packageDir, installed.packageJson),
|
||||
);
|
||||
}
|
||||
|
||||
export function isRuntimeDepSatisfiedInAnyRoot(
|
||||
|
||||
@@ -3198,6 +3198,58 @@ describe("ensureBundledPluginRuntimeDeps", () => {
|
||||
expect(installRoot).not.toBe(pluginRoot);
|
||||
});
|
||||
|
||||
it("repairs package-level mirrors when an installed package entry file is missing", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "openclaw",
|
||||
version: "2026.4.27",
|
||||
dependencies: { ajv: "8.20.0" },
|
||||
openclaw: {
|
||||
bundle: {
|
||||
mirroredRootRuntimeDependencies: ["ajv"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const pluginRoot = writeBundledPluginPackage({
|
||||
packageRoot,
|
||||
pluginId: "browser",
|
||||
deps: {},
|
||||
enabledByDefault: true,
|
||||
});
|
||||
const env = { OPENCLAW_PLUGIN_STAGE_DIR: stageDir };
|
||||
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env });
|
||||
writeGeneratedRuntimeDepsManifest(installRoot, ["ajv@8.20.0"]);
|
||||
const ajvRoot = path.join(installRoot, "node_modules", "ajv");
|
||||
fs.mkdirSync(ajvRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(ajvRoot, "package.json"),
|
||||
JSON.stringify({ name: "ajv", version: "8.20.0", main: "dist/ajv.js" }),
|
||||
);
|
||||
|
||||
const calls: BundledRuntimeDepsInstallParams[] = [];
|
||||
const result = ensureBundledPluginRuntimeDeps({
|
||||
env,
|
||||
pluginId: "browser",
|
||||
pluginRoot,
|
||||
installDeps: (params) => {
|
||||
calls.push(params);
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.installedSpecs).toEqual(["ajv@8.20.0"]);
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
installRoot,
|
||||
missingSpecs: ["ajv@8.20.0"],
|
||||
installSpecs: ["ajv@8.20.0"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("mirrors sqlite-vec into the packaged default memory runtime deps", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
|
||||
Reference in New Issue
Block a user