mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(plugins): recover source-only install shadows
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/install: let official plugin reinstall recovery repair source-only installed runtime shadows, so `openclaw plugins install npm:@openclaw/discord --force` can replace the bad package instead of stopping at stale config validation. Thanks @vincentkoc.
|
||||
- Plugins/commands: allow the official ClawHub Codex plugin package to keep reserved `/codex` command ownership, matching the existing npm-managed Codex package behavior. Thanks @vincentkoc.
|
||||
- Plugins/commands: scope QQBot framework slash commands to the QQBot channel so `/bot-*` command handlers and native specs do not leak onto unrelated chat surfaces. Thanks @vincentkoc.
|
||||
- fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987.
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { parseNpmPrefixSpec, resolveFileNpmSpecToLocalPath } from "./plugins-command-helpers.js";
|
||||
|
||||
type PluginInstallInvalidConfigPolicy = "deny" | "allow-bundled-recovery";
|
||||
type PluginInstallInvalidConfigPolicy = "deny" | "allow-plugin-recovery";
|
||||
|
||||
export type PluginInstallRequestContext = {
|
||||
rawSpec: string;
|
||||
@@ -264,5 +264,5 @@ export function resolvePluginInstallInvalidConfigPolicy(
|
||||
if (!request) {
|
||||
return "deny";
|
||||
}
|
||||
return request.allowInvalidConfigRecovery === true ? "allow-bundled-recovery" : "deny";
|
||||
return request.allowInvalidConfigRecovery === true ? "allow-plugin-recovery" : "deny";
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ function isTerminalPluginInstallSecurityFailure(code?: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isAllowedBundledRecoveryIssue(
|
||||
function isAllowedPluginRecoveryIssue(
|
||||
issue: { path?: string; message?: string },
|
||||
request: PluginInstallRequestContext,
|
||||
): boolean {
|
||||
@@ -434,7 +434,10 @@ function isAllowedBundledRecoveryIssue(
|
||||
issue.message === `unknown channel id: ${pluginId}`) ||
|
||||
(issue.path === "plugins.load.paths" &&
|
||||
typeof issue.message === "string" &&
|
||||
issue.message.includes("plugin path not found"))
|
||||
issue.message.includes("plugin path not found")) ||
|
||||
(issue.path === "plugins" &&
|
||||
typeof issue.message === "string" &&
|
||||
issue.message.includes("requires compiled runtime output"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -448,7 +451,7 @@ async function loadConfigFromSnapshotForInstall(
|
||||
request: PluginInstallRequestContext,
|
||||
snapshot: Awaited<ReturnType<typeof readConfigFileSnapshot>>,
|
||||
): Promise<ConfigSnapshotForInstallPersist> {
|
||||
if (resolvePluginInstallInvalidConfigPolicy(request) !== "allow-bundled-recovery") {
|
||||
if (resolvePluginInstallInvalidConfigPolicy(request) !== "allow-plugin-recovery") {
|
||||
throw buildInvalidPluginInstallConfigError(
|
||||
"Config invalid; run `openclaw doctor --fix` before installing plugins.",
|
||||
);
|
||||
@@ -462,11 +465,11 @@ async function loadConfigFromSnapshotForInstall(
|
||||
if (
|
||||
snapshot.legacyIssues.length > 0 ||
|
||||
snapshot.issues.length === 0 ||
|
||||
snapshot.issues.some((issue) => !isAllowedBundledRecoveryIssue(issue, request))
|
||||
snapshot.issues.some((issue) => !isAllowedPluginRecoveryIssue(issue, request))
|
||||
) {
|
||||
const pluginLabel = request.bundledPluginId ?? "the requested plugin";
|
||||
throw buildInvalidPluginInstallConfigError(
|
||||
`Config invalid outside the bundled recovery path for ${pluginLabel}; run \`openclaw doctor --fix\` before reinstalling it.`,
|
||||
`Config invalid outside the plugin recovery path for ${pluginLabel}; run \`openclaw doctor --fix\` before reinstalling it.`,
|
||||
);
|
||||
}
|
||||
let nextConfig = snapshot.config;
|
||||
|
||||
@@ -151,6 +151,36 @@ describe("loadConfigForInstall", () => {
|
||||
expect(result).toEqual({ config: snapshotCfg, baseHash: "abc" });
|
||||
});
|
||||
|
||||
it("allows official plugin reinstall recovery from source-only runtime shadows", async () => {
|
||||
const snapshotCfg = {
|
||||
plugins: { installs: { discord: { source: "npm", installPath: "/bad/discord" } } },
|
||||
} as unknown as OpenClawConfig;
|
||||
readConfigFileSnapshotMock.mockResolvedValue(
|
||||
makeSnapshot({
|
||||
parsed: { plugins: { installs: { discord: {} } } },
|
||||
config: snapshotCfg,
|
||||
issues: [
|
||||
{
|
||||
path: "plugins",
|
||||
message:
|
||||
"plugin: installed plugin package requires compiled runtime output for TypeScript entry index.ts: expected ./dist/index.js, ./dist/index.mjs, ./dist/index.cjs, index.js, index.mjs, index.cjs",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const request = resolvePluginInstallRequestContext({
|
||||
rawSpec: "npm:@openclaw/discord",
|
||||
});
|
||||
if (!request.ok) {
|
||||
throw new Error(request.error);
|
||||
}
|
||||
|
||||
const result = await loadConfigForInstall(request.request);
|
||||
expect(collectChannelDoctorStaleConfigMutationsMock).toHaveBeenCalledWith(snapshotCfg);
|
||||
expect(result).toEqual({ config: snapshotCfg, baseHash: "abc" });
|
||||
});
|
||||
|
||||
it("allows explicit repo-checkout bundled-plugin reinstall recovery", async () => {
|
||||
const snapshotCfg = { plugins: {} } as OpenClawConfig;
|
||||
readConfigFileSnapshotMock.mockResolvedValue(
|
||||
@@ -182,7 +212,7 @@ describe("loadConfigForInstall", () => {
|
||||
);
|
||||
|
||||
await expect(loadConfigForInstall(discordNpmRequest)).rejects.toThrow(
|
||||
"Config invalid outside the bundled recovery path for discord",
|
||||
"Config invalid outside the plugin recovery path for discord",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ function shouldAllowInvalidConfigForAction(actionCommand: Command, commandPath:
|
||||
commandPath,
|
||||
argv: process.argv,
|
||||
}),
|
||||
) === "allow-bundled-recovery"
|
||||
) === "allow-plugin-recovery"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user