mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 16:57:27 +00:00
fix(plugins): prioritize bundled duplicates in auto-discovery
Landed from contributor PR #29710 by @Sid-Qin. Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
This commit is contained in:
@@ -92,6 +92,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/Discovery precedence: load bundled plugins before auto-discovered global extensions so bundled channel plugins win duplicate-ID resolution by default (explicit `plugins.load.paths` overrides remain highest precedence), with loader regression coverage. Landed from contributor PR #29710 by @Sid-Qin. Thanks @Sid-Qin.
|
||||
- Discord/Reconnect integrity: release Discord message listener lane immediately while preserving serialized handler execution, add HELLO-stall resume-first recovery with bounded fresh-identify fallback after repeated stalls, and extend lifecycle/listener regression coverage for forced reconnect scenarios. Landed from contributor PR #29508 by @cgdusek. Thanks @cgdusek.
|
||||
- Security/Skills: harden skill installer metadata parsing by rejecting unsafe installer specs (brew/node/go/uv/download) and constrain plugin-declared skill directories to the plugin root (including symlink-escape checks), with regression coverage.
|
||||
- ACP/Harness thread spawn routing: force ACP harness thread creation through `sessions_spawn` (`runtime: "acp"`, `thread: true`) and explicitly forbid `message action=thread-create` for ACP harness requests, avoiding misrouted `Unknown channel` errors. (#30957) Thanks @dutifulbob.
|
||||
|
||||
@@ -609,16 +609,6 @@ export function discoverOpenClawPlugins(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const globalDir = path.join(resolveConfigDir(), "extensions");
|
||||
discoverInDirectory({
|
||||
dir: globalDir,
|
||||
origin: "global",
|
||||
ownershipUid: params.ownershipUid,
|
||||
candidates,
|
||||
diagnostics,
|
||||
seen,
|
||||
});
|
||||
|
||||
const bundledDir = resolveBundledPluginsDir();
|
||||
if (bundledDir) {
|
||||
discoverInDirectory({
|
||||
@@ -631,5 +621,17 @@ export function discoverOpenClawPlugins(params: {
|
||||
});
|
||||
}
|
||||
|
||||
// Keep auto-discovered global extensions behind bundled plugins.
|
||||
// Users can still intentionally override via plugins.load.paths (origin=config).
|
||||
const globalDir = path.join(resolveConfigDir(), "extensions");
|
||||
discoverInDirectory({
|
||||
dir: globalDir,
|
||||
origin: "global",
|
||||
ownershipUid: params.ownershipUid,
|
||||
candidates,
|
||||
diagnostics,
|
||||
seen,
|
||||
});
|
||||
|
||||
return { candidates, diagnostics };
|
||||
}
|
||||
|
||||
@@ -569,6 +569,49 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(loaded?.origin).toBe("config");
|
||||
expect(overridden?.origin).toBe("bundled");
|
||||
});
|
||||
|
||||
it("prefers bundled plugin over auto-discovered global duplicate ids", () => {
|
||||
const bundledDir = makeTempDir();
|
||||
writePlugin({
|
||||
id: "feishu",
|
||||
body: `export default { id: "feishu", register() {} };`,
|
||||
dir: bundledDir,
|
||||
filename: "index.js",
|
||||
});
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
|
||||
const stateDir = makeTempDir();
|
||||
withEnv({ OPENCLAW_STATE_DIR: stateDir, CLAWDBOT_STATE_DIR: undefined }, () => {
|
||||
const globalDir = path.join(stateDir, "extensions", "feishu");
|
||||
fs.mkdirSync(globalDir, { recursive: true });
|
||||
writePlugin({
|
||||
id: "feishu",
|
||||
body: `export default { id: "feishu", register() {} };`,
|
||||
dir: globalDir,
|
||||
filename: "index.js",
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["feishu"],
|
||||
entries: {
|
||||
feishu: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const entries = registry.plugins.filter((entry) => entry.id === "feishu");
|
||||
const loaded = entries.find((entry) => entry.status === "loaded");
|
||||
const overridden = entries.find((entry) => entry.status === "disabled");
|
||||
expect(loaded?.origin).toBe("bundled");
|
||||
expect(overridden?.origin).toBe("global");
|
||||
expect(overridden?.error).toContain("overridden by bundled plugin");
|
||||
});
|
||||
});
|
||||
|
||||
it("warns when plugins.allow is empty and non-bundled plugins are discoverable", () => {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||
const plugin = writePlugin({
|
||||
|
||||
Reference in New Issue
Block a user