fix: install ClawHub package dependencies

This commit is contained in:
Peter Steinberger
2026-05-02 06:56:58 +01:00
parent bc42952c31
commit 5ac0ff1812
3 changed files with 20 additions and 4 deletions

View File

@@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
- Web search/Exa: accept `plugins.entries.exa.config.webSearch.baseUrl`, normalize it to the Exa `/search` endpoint, and partition cached results by endpoint. Fixes #54928 and supersedes #54939. Thanks @mrpl327 and @lyfuci.
- Web search/MiniMax: include MiniMax Search in the web-search setup flow and let `MINIMAX_API_KEY` participate in MiniMax Search auto-detection. Supersedes #65828. Thanks @Jah-yee.
- Plugins/ClawHub: preserve official source-linked trust through archive installs, so OpenClaw can install trusted ClawHub plugin packages that trigger the built-in dangerous-pattern scanner. Thanks @vincentkoc.
- Plugins/ClawHub: install package runtime dependencies for archive-backed plugin installs, so ClawHub packages such as WhatsApp load declared dependencies after download. Thanks @vincentkoc.
- Providers/LM Studio: allow `models.providers.lmstudio.params.preload: false` to skip OpenClaw's native model-load call so LM Studio JIT loading, idle TTL, and auto-evict can own model lifecycle. Fixes #75921. Thanks @garyd9.
- Telegram: inherit the process DNS result order for Bot API transport and downgrade recovered sticky IPv4 fallback promotions to debug logs, while keeping pinned-IP escalation warnings visible. Fixes #75904. Thanks @highfly-hi and @neeravmakwana.
- Sessions: keep durable external conversation pointers, including group and thread-scoped chat sessions, out of age, count, and disk-budget maintenance eviction while still allowing synthetic runtime entries to age out. Fixes #58088. Thanks @drinkflav.

View File

@@ -622,7 +622,7 @@ beforeEach(() => {
});
describe("installPluginFromArchive", () => {
it("does not run npm for package archive runtime dependencies", async () => {
it("installs package archive runtime dependencies", async () => {
const result = await installArchivePackageAndReturnResult({
packageJson: {
name: "archive-with-deps",
@@ -635,7 +635,12 @@ describe("installPluginFromArchive", () => {
});
expect(result.ok).toBe(true);
expect(vi.mocked(runCommandWithTimeout)).not.toHaveBeenCalled();
expect(vi.mocked(runCommandWithTimeout)).toHaveBeenCalledWith(
expect.arrayContaining(["npm", "install"]),
expect.objectContaining({
cwd: expect.stringContaining(".openclaw-install-stage-"),
}),
);
});
it("installs scoped archives, rejects duplicate installs, and allows updates", async () => {

View File

@@ -178,6 +178,13 @@ function buildDirectoryInstallResult(params: {
};
}
function hasPackageRuntimeDependencies(manifest: PackageManifest): boolean {
return (
Object.keys(manifest.dependencies ?? {}).length > 0 ||
Object.keys(manifest.optionalDependencies ?? {}).length > 0
);
}
function buildBlockedInstallResult(params: {
blocked: NonNullable<NonNullable<InstallSecurityScanResult>["blocked"]>;
}): Extract<InstallPluginResult, { ok: false }> {
@@ -560,6 +567,7 @@ type ValidatedPackagePlugin = {
manifestName?: string;
version?: string;
extensions: string[];
hasRuntimeDependencies: boolean;
peerDependencies: Record<string, string>;
};
@@ -719,6 +727,7 @@ async function validatePackagePluginInstallSource(params: {
manifestName: pkgName || undefined,
version: typeof manifest.version === "string" ? manifest.version : undefined,
extensions,
hasRuntimeDependencies: hasPackageRuntimeDependencies(manifest),
peerDependencies: manifest.peerDependencies ?? {},
},
};
@@ -855,8 +864,9 @@ async function installPluginFromPackageDir(
mode: preparedTarget.effectiveMode,
dryRun,
copyErrorPrefix: "failed to copy plugin",
hasDeps: false,
depsLogMessage: "",
hasDeps:
plugin.hasRuntimeDependencies && params.installPolicyRequest?.kind === "plugin-archive",
depsLogMessage: "Installing plugin dependencies…",
nameEncoder: encodePluginInstallDirName,
afterInstall: async (installedDir) => {
return await scanAndLinkInstalledPackage({