mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Plugins: allowlist plugins when enabling from CLI
This commit is contained in:
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
||||
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
|
||||
- Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.
|
||||
- Plugins/CLI: make `openclaw plugins enable` and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl.
|
||||
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
|
||||
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
|
||||
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadConfig, writeConfigFile } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { resolveArchiveKind } from "../infra/archive.js";
|
||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import { installPluginFromNpmSpec, installPluginFromPath } from "../plugins/install.js";
|
||||
import { recordPluginInstall } from "../plugins/installs.js";
|
||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||
@@ -135,22 +136,6 @@ function createPluginInstallLogger(): { info: (msg: string) => void; warn: (msg:
|
||||
};
|
||||
}
|
||||
|
||||
function enablePluginInConfig(config: OpenClawConfig, pluginId: string): OpenClawConfig {
|
||||
return {
|
||||
...config,
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
entries: {
|
||||
...config.plugins?.entries,
|
||||
[pluginId]: {
|
||||
...(config.plugins?.entries?.[pluginId] as object | undefined),
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function logSlotWarnings(warnings: string[]) {
|
||||
if (warnings.length === 0) {
|
||||
return;
|
||||
@@ -352,24 +337,21 @@ export function registerPluginsCli(program: Command) {
|
||||
.argument("<id>", "Plugin id")
|
||||
.action(async (id: string) => {
|
||||
const cfg = loadConfig();
|
||||
let next: OpenClawConfig = {
|
||||
...cfg,
|
||||
plugins: {
|
||||
...cfg.plugins,
|
||||
entries: {
|
||||
...cfg.plugins?.entries,
|
||||
[id]: {
|
||||
...(cfg.plugins?.entries as Record<string, { enabled?: boolean }> | undefined)?.[id],
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const enableResult = enablePluginInConfig(cfg, id);
|
||||
let next: OpenClawConfig = enableResult.config;
|
||||
const slotResult = applySlotSelectionForPlugin(next, id);
|
||||
next = slotResult.config;
|
||||
await writeConfigFile(next);
|
||||
logSlotWarnings(slotResult.warnings);
|
||||
defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
||||
if (enableResult.enabled) {
|
||||
defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(
|
||||
theme.warn(
|
||||
`Plugin "${id}" could not be enabled (${enableResult.reason ?? "unknown reason"}).`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
plugins
|
||||
@@ -568,7 +550,7 @@ export function registerPluginsCli(program: Command) {
|
||||
},
|
||||
},
|
||||
probe.pluginId,
|
||||
);
|
||||
).config;
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: probe.pluginId,
|
||||
source: "path",
|
||||
@@ -597,7 +579,7 @@ export function registerPluginsCli(program: Command) {
|
||||
// force a rescan so config validation sees the freshly installed plugin.
|
||||
clearPluginManifestRegistryCache();
|
||||
|
||||
let next = enablePluginInConfig(cfg, result.pluginId);
|
||||
let next = enablePluginInConfig(cfg, result.pluginId).config;
|
||||
const source: "archive" | "path" = resolveArchiveKind(resolved) ? "archive" : "path";
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: result.pluginId,
|
||||
@@ -648,7 +630,7 @@ export function registerPluginsCli(program: Command) {
|
||||
// Ensure config validation sees newly installed plugin(s) even if the cache was warmed at startup.
|
||||
clearPluginManifestRegistryCache();
|
||||
|
||||
let next = enablePluginInConfig(cfg, result.pluginId);
|
||||
let next = enablePluginInConfig(cfg, result.pluginId).config;
|
||||
const resolvedSpec = result.npmResolution?.resolvedSpec;
|
||||
const recordSpec = opts.pin && resolvedSpec ? resolvedSpec : raw;
|
||||
if (opts.pin && !resolvedSpec) {
|
||||
|
||||
34
src/plugins/enable.test.ts
Normal file
34
src/plugins/enable.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { enablePluginInConfig } from "./enable.js";
|
||||
|
||||
describe("enablePluginInConfig", () => {
|
||||
it("enables a plugin entry", () => {
|
||||
const cfg: OpenClawConfig = {};
|
||||
const result = enablePluginInConfig(cfg, "google-antigravity-auth");
|
||||
expect(result.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.["google-antigravity-auth"]?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("adds plugin to allowlist when allowlist is configured", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
plugins: {
|
||||
allow: ["memory-core"],
|
||||
},
|
||||
};
|
||||
const result = enablePluginInConfig(cfg, "google-antigravity-auth");
|
||||
expect(result.enabled).toBe(true);
|
||||
expect(result.config.plugins?.allow).toEqual(["memory-core", "google-antigravity-auth"]);
|
||||
});
|
||||
|
||||
it("refuses enable when plugin is denylisted", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
plugins: {
|
||||
deny: ["google-antigravity-auth"],
|
||||
},
|
||||
};
|
||||
const result = enablePluginInConfig(cfg, "google-antigravity-auth");
|
||||
expect(result.enabled).toBe(false);
|
||||
expect(result.reason).toBe("blocked by denylist");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user