mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 02:22:46 +00:00
fix(plugins): prefer installed memory tool owners
This commit is contained in:
@@ -80,6 +80,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Config: serialize and retry semantic config mutations centrally, so concurrent commands can rebase safe changes instead of clobbering or hand-rolling command-local retry loops. (#76601)
|
||||
- Require approval for setup-code device pairing [AI]. (#81292) Thanks @pgondhi987.
|
||||
- Plugins/install: preserve third-party peer dependencies in the managed npm root when later plugin installs or updates recalculate the shared dependency tree. Thanks @shakkernerd.
|
||||
- Plugins/memory: prefer the npm-installed memory-lancedb plugin over the bundled fallback during duplicate resolution, keeping Active Memory's `memory_recall` tool visible after managed installs. Fixes #81193. Thanks @julio-arcila.
|
||||
- Plugins/uninstall: prune managed third-party peer dependencies after their owning npm plugin is removed, without blocking plugin cleanup on peer-prune failures.
|
||||
- Docker: pin setup-time container paths so stale host `.env` OpenClaw paths cannot leak into Linux containers. Fixes #80381. (#81105) Thanks @brokemac79.
|
||||
- Channels/WeCom: refresh the official onboarding install to `@wecom/wecom-openclaw-plugin@2026.5.7` and update existing managed npm installs instead of failing on the package directory. Fixes #79884. (#80390) Thanks @brokemac79.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
@@ -66,6 +67,7 @@ function matchesPathMatcher(matcher: PathMatcher, sourcePath: string): boolean {
|
||||
export function buildProvenanceIndex(params: {
|
||||
normalizedLoadPaths: string[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
installRecords?: Record<string, PluginInstallRecord>;
|
||||
}): PluginProvenanceIndex {
|
||||
const loadPathMatcher = createPathMatcher();
|
||||
for (const loadPath of params.normalizedLoadPaths) {
|
||||
@@ -73,7 +75,10 @@ export function buildProvenanceIndex(params: {
|
||||
}
|
||||
|
||||
const installRules = new Map<string, InstallTrackingRule>();
|
||||
const installs = loadInstalledPluginIndexInstallRecordsSync({ env: params.env });
|
||||
const installs = {
|
||||
...loadInstalledPluginIndexInstallRecordsSync({ env: params.env }),
|
||||
...params.installRecords,
|
||||
};
|
||||
for (const [pluginId, install] of Object.entries(installs)) {
|
||||
const rule: InstallTrackingRule = {
|
||||
trackedWithoutPaths: false,
|
||||
|
||||
@@ -6231,6 +6231,91 @@ module.exports = {
|
||||
expectDuplicateWarning: false,
|
||||
assert: expectPluginSourcePrecedence,
|
||||
},
|
||||
{
|
||||
label: "transient installed memory plugin beats bundled duplicate",
|
||||
pluginId: "memory-lancedb",
|
||||
bundledFilename: "index.cjs",
|
||||
loadRegistry: () => {
|
||||
writeBundledPlugin({
|
||||
id: "memory-lancedb",
|
||||
body: memoryPluginBody("memory-lancedb"),
|
||||
});
|
||||
return withStateDir((stateDir) => {
|
||||
const globalDir = path.join(stateDir, "node_modules", "@openclaw", "memory-lancedb");
|
||||
mkdirSafe(globalDir);
|
||||
const globalPlugin = writePlugin({
|
||||
id: "memory-lancedb",
|
||||
body: `module.exports = {
|
||||
id: "memory-lancedb",
|
||||
kind: "memory",
|
||||
register(api) {
|
||||
api.registerTool({
|
||||
name: "memory_recall",
|
||||
description: "Recall memories",
|
||||
parameters: {},
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }] }),
|
||||
});
|
||||
},
|
||||
};`,
|
||||
dir: globalDir,
|
||||
filename: "index.cjs",
|
||||
});
|
||||
updatePluginManifest(globalPlugin, {
|
||||
kind: "memory",
|
||||
contracts: { tools: ["memory_recall"] },
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(globalDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/memory-lancedb",
|
||||
version: "2026.5.12-beta.1",
|
||||
openclaw: { extensions: ["./index.cjs"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
return loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["memory-lancedb"],
|
||||
slots: { memory: "memory-lancedb" },
|
||||
entries: {
|
||||
"memory-lancedb": { enabled: true },
|
||||
},
|
||||
installs: {
|
||||
"memory-lancedb": {
|
||||
source: "npm",
|
||||
spec: "@openclaw/memory-lancedb",
|
||||
resolvedName: "@openclaw/memory-lancedb",
|
||||
resolvedVersion: "2026.5.12-beta.1",
|
||||
installPath: globalDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
expectedLoadedOrigin: "global",
|
||||
expectedDisabledOrigin: "bundled",
|
||||
expectedDisabledError: "overridden by global plugin",
|
||||
expectDuplicateWarning: false,
|
||||
assert: (
|
||||
registry: PluginRegistry,
|
||||
scenario: Parameters<typeof expectPluginSourcePrecedence>[1],
|
||||
) => {
|
||||
expectPluginSourcePrecedence(registry, scenario);
|
||||
expect(
|
||||
registry.tools.flatMap((entry) => entry.names),
|
||||
scenario.label,
|
||||
).toContain("memory_recall");
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
runRegistryScenarios(scenarios, (scenario) => scenario.loadRegistry());
|
||||
|
||||
@@ -1712,6 +1712,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
const provenance = buildProvenanceIndex({
|
||||
normalizedLoadPaths: normalized.loadPaths,
|
||||
env,
|
||||
installRecords,
|
||||
});
|
||||
|
||||
const manifestByRoot = new Map(
|
||||
@@ -2556,6 +2557,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
const provenance = buildProvenanceIndex({
|
||||
normalizedLoadPaths: normalized.loadPaths,
|
||||
env,
|
||||
installRecords,
|
||||
});
|
||||
const manifestByRoot = new Map(
|
||||
manifestRegistry.plugins.map((record) => [record.rootDir, record]),
|
||||
|
||||
@@ -651,10 +651,14 @@ function matchesInstalledPluginRecord(params: {
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
const candidateSource = resolveUserPath(params.candidate.source, params.env);
|
||||
const resolvedCandidateSource = resolveUserPath(params.candidate.source, params.env);
|
||||
const candidateSource = safeRealpathSync(resolvedCandidateSource) ?? resolvedCandidateSource;
|
||||
const trackedPaths = [record.installPath, record.sourcePath]
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
||||
.map((entry) => resolveUserPath(entry, params.env));
|
||||
.map((entry) => {
|
||||
const resolved = resolveUserPath(entry, params.env);
|
||||
return safeRealpathSync(resolved) ?? resolved;
|
||||
});
|
||||
if (trackedPaths.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user