mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix: retain local memory runtime deps
This commit is contained in:
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Agents/subagents: bound automatic orphan recovery with persisted recovery attempts and a wedged-session tombstone, and teach task maintenance/doctor to reconcile those sessions so restart loops no longer require manual `sessions.json` surgery. Fixes #74864. Thanks @solosage1.
|
||||
- Plugins/runtime-deps: keep bundled provider policy config loading from staging plugin runtime dependencies, so config reads no longer fail on locked-down `/var/lib/openclaw/plugin-runtime-deps` directories. Fixes #74971. Thanks @eurojojo.
|
||||
- Memory/runtime-deps: retain the native `node-llama-cpp` runtime only when local memory search is configured, so packaged installs can repair local embeddings without relying on unreachable global npm installs. Fixes #74777. Thanks @LLagoon3.
|
||||
- Gateway/startup: skip pre-bind web-fetch provider discovery for credential-free `tools.web.fetch` config, so Docker/Kubernetes gateways bind even when optional fetch limits are present. Fixes #74896. Thanks @KoykL.
|
||||
- Slack: require bot-authored room messages with `allowBots=true` to come from an explicitly channel-allowlisted bot or from a room where an explicit Slack owner is present, so broad bot relays cannot run unattended. Fixes #59284. Thanks @andrewhong-translucent.
|
||||
- Signal: derive `getAttachment` HTTP response caps from `channels.signal.mediaMaxMb` with base64 headroom, so inbound photos and videos no longer drop behind the 1 MiB RPC default. Fixes #73564. Thanks @heyhudson.
|
||||
|
||||
@@ -33,8 +33,9 @@ For multi-endpoint setups, `provider` can also be a custom
|
||||
`models.providers.<id>` entry, such as `ollama-5080`, when that provider sets
|
||||
`api: "ollama"` or another embedding adapter owner.
|
||||
|
||||
For local embeddings with no API key, install the optional `node-llama-cpp`
|
||||
runtime package next to OpenClaw and use `provider: "local"`.
|
||||
For local embeddings with no API key, set `provider: "local"`. Packaged
|
||||
installs retain the native `node-llama-cpp` runtime in OpenClaw's managed plugin
|
||||
runtime-deps tree; run `openclaw doctor --fix` if that tree needs repair.
|
||||
|
||||
Some OpenAI-compatible embedding endpoints require asymmetric labels such as
|
||||
`input_type: "query"` for searches and `input_type: "document"` or `"passage"`
|
||||
|
||||
@@ -284,7 +284,7 @@ For custom OpenAI-compatible endpoints or overriding provider defaults:
|
||||
| `local.modelCacheDir` | `string` | node-llama-cpp default | Cache dir for downloaded models |
|
||||
| `local.contextSize` | `number \| "auto"` | `4096` | Context window size for the embedding context. 4096 covers typical chunks (128–512 tokens) while bounding non-weight VRAM. Lower to 1024–2048 on constrained hosts. `"auto"` uses the model's trained maximum — not recommended for 8B+ models (Qwen3-Embedding-8B: 40 960 tokens → ~32 GB VRAM vs ~8.8 GB at 4096). |
|
||||
|
||||
Default model: `embeddinggemma-300m-qat-Q8_0.gguf` (~0.6 GB, auto-downloaded). Requires native build: `pnpm approve-builds` then `pnpm rebuild node-llama-cpp`.
|
||||
Default model: `embeddinggemma-300m-qat-Q8_0.gguf` (~0.6 GB, auto-downloaded). Packaged installs repair the native `node-llama-cpp` runtime through managed plugin runtime deps when `provider: "local"` is configured. Source checkouts still require native build approval: `pnpm approve-builds` then `pnpm rebuild node-llama-cpp`.
|
||||
|
||||
Use the standalone CLI to verify the same provider path the Gateway uses:
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"contracts": {
|
||||
"memoryEmbeddingProviders": ["local"]
|
||||
},
|
||||
"runtimeDependencies": {
|
||||
"localMemoryEmbedding": ["node-llama-cpp@3.18.1"]
|
||||
},
|
||||
"commandAliases": [
|
||||
{
|
||||
"name": "dreaming",
|
||||
|
||||
@@ -59,7 +59,7 @@ function formatLocalSetupError(err: unknown): string {
|
||||
"To enable local embeddings:",
|
||||
"1) Use Node 24 (recommended for installs/updates; Node 22 LTS, currently 22.14+, remains supported)",
|
||||
missing
|
||||
? `2) Install optional local embedding runtime next to OpenClaw: npm i -g ${NODE_LLAMA_CPP_INSTALL_SPEC}`
|
||||
? `2) Run openclaw doctor --fix to repair managed plugin runtime deps for ${NODE_LLAMA_CPP_INSTALL_SPEC}`
|
||||
: null,
|
||||
`3) If you use pnpm: pnpm approve-builds (select ${NODE_LLAMA_CPP_RUNTIME_PACKAGE}), then pnpm rebuild ${NODE_LLAMA_CPP_RUNTIME_PACKAGE}`,
|
||||
...listRemoteEmbeddingSetupHints(),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
collectPackageRuntimeDeps,
|
||||
normalizeInstallableRuntimeDepName,
|
||||
parseInstallableRuntimeDep,
|
||||
parseInstallableRuntimeDepSpec,
|
||||
type RuntimeDepEntry,
|
||||
} from "./bundled-runtime-deps-specs.js";
|
||||
import {
|
||||
@@ -30,6 +31,7 @@ export type BundledPluginRuntimeDepsManifest = {
|
||||
enabledByDefault: boolean;
|
||||
id?: string;
|
||||
legacyPluginIds: string[];
|
||||
localMemoryEmbeddingRuntimeDeps: RuntimeDepEntry[];
|
||||
modelSupport?: BundledPluginRuntimeDepsModelSupport;
|
||||
providers: string[];
|
||||
};
|
||||
@@ -110,6 +112,9 @@ function readBundledPluginRuntimeDepsManifest(
|
||||
const manifest = readRuntimeDepsJsonObject(path.join(pluginDir, "openclaw.plugin.json"));
|
||||
const channels = manifest?.channels;
|
||||
const legacyPluginIds = manifest?.legacyPluginIds;
|
||||
const localMemoryEmbeddingRuntimeDeps = readBundledPluginLocalMemoryEmbeddingRuntimeDeps(
|
||||
manifest?.runtimeDependencies,
|
||||
);
|
||||
const modelSupport = readBundledPluginRuntimeDepsModelSupport(manifest?.modelSupport);
|
||||
const providers = manifest?.providers;
|
||||
const runtimeDepsManifest = {
|
||||
@@ -123,6 +128,7 @@ function readBundledPluginRuntimeDepsManifest(
|
||||
(entry): entry is string => typeof entry === "string" && entry !== "",
|
||||
)
|
||||
: [],
|
||||
localMemoryEmbeddingRuntimeDeps,
|
||||
...(modelSupport ? { modelSupport } : {}),
|
||||
providers: Array.isArray(providers)
|
||||
? providers.filter((entry): entry is string => typeof entry === "string" && entry !== "")
|
||||
@@ -132,6 +138,24 @@ function readBundledPluginRuntimeDepsManifest(
|
||||
return runtimeDepsManifest;
|
||||
}
|
||||
|
||||
function readBundledPluginLocalMemoryEmbeddingRuntimeDeps(value: unknown): RuntimeDepEntry[] {
|
||||
if (!isRecord(value)) {
|
||||
return [];
|
||||
}
|
||||
const specs = value.localMemoryEmbedding;
|
||||
if (!Array.isArray(specs)) {
|
||||
return [];
|
||||
}
|
||||
return specs.map((spec) => {
|
||||
if (typeof spec !== "string") {
|
||||
throw new Error(
|
||||
"openclaw.plugin.json runtimeDependencies.localMemoryEmbedding must contain strings",
|
||||
);
|
||||
}
|
||||
return { ...parseInstallableRuntimeDepSpec(spec), pluginIds: [] };
|
||||
});
|
||||
}
|
||||
|
||||
function readBundledPluginRuntimeDepsModelSupport(
|
||||
value: unknown,
|
||||
): BundledPluginRuntimeDepsModelSupport | undefined {
|
||||
@@ -353,6 +377,30 @@ function collectConfiguredProviderIds(config: OpenClawConfig): Set<string> {
|
||||
return collectConfiguredRuntimeDepsTargets(config).providerIds;
|
||||
}
|
||||
|
||||
function memorySearchConfigUsesProvider(
|
||||
value: { enabled?: boolean; provider?: string } | undefined,
|
||||
providerId: string,
|
||||
): boolean {
|
||||
return (
|
||||
value?.enabled !== false && normalizeOptionalLowercaseString(value?.provider) === providerId
|
||||
);
|
||||
}
|
||||
|
||||
function isMemoryEmbeddingProviderConfiguredForRuntimeDeps(
|
||||
config: OpenClawConfig | undefined,
|
||||
providerId: string,
|
||||
): boolean {
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
if (memorySearchConfigUsesProvider(config.agents?.defaults?.memorySearch, providerId)) {
|
||||
return true;
|
||||
}
|
||||
return (config.agents?.list ?? []).some((agent) =>
|
||||
memorySearchConfigUsesProvider(agent.memorySearch, providerId),
|
||||
);
|
||||
}
|
||||
|
||||
function matchesBundledRuntimeDepsModelSupport(
|
||||
manifest: BundledPluginRuntimeDepsManifest,
|
||||
modelId: string,
|
||||
@@ -665,20 +713,32 @@ export function collectBundledPluginRuntimeDeps(params: {
|
||||
continue;
|
||||
}
|
||||
includedPluginIds.add(pluginId);
|
||||
const manifest = readBundledPluginRuntimeDepsManifest(pluginDir, manifestCache);
|
||||
const packageJson = readRuntimeDepsJsonObject(path.join(pluginDir, "package.json"));
|
||||
if (!packageJson) {
|
||||
continue;
|
||||
}
|
||||
for (const [name, rawVersion] of Object.entries(collectPackageRuntimeDeps(packageJson))) {
|
||||
const dep = parseInstallableRuntimeDep(name, rawVersion);
|
||||
if (!dep) {
|
||||
continue;
|
||||
if (packageJson) {
|
||||
for (const [name, rawVersion] of Object.entries(collectPackageRuntimeDeps(packageJson))) {
|
||||
const dep = parseInstallableRuntimeDep(name, rawVersion);
|
||||
if (!dep) {
|
||||
continue;
|
||||
}
|
||||
const byVersion = versionMap.get(dep.name) ?? new Map<string, Set<string>>();
|
||||
const pluginIds = byVersion.get(dep.version) ?? new Set<string>();
|
||||
pluginIds.add(pluginId);
|
||||
byVersion.set(dep.version, pluginIds);
|
||||
versionMap.set(dep.name, byVersion);
|
||||
}
|
||||
}
|
||||
if (
|
||||
manifest.localMemoryEmbeddingRuntimeDeps.length > 0 &&
|
||||
isMemoryEmbeddingProviderConfiguredForRuntimeDeps(params.config, "local")
|
||||
) {
|
||||
for (const dep of manifest.localMemoryEmbeddingRuntimeDeps) {
|
||||
const byVersion = versionMap.get(dep.name) ?? new Map<string, Set<string>>();
|
||||
const pluginIds = byVersion.get(dep.version) ?? new Set<string>();
|
||||
pluginIds.add(pluginId);
|
||||
byVersion.set(dep.version, pluginIds);
|
||||
versionMap.set(dep.name, byVersion);
|
||||
}
|
||||
const byVersion = versionMap.get(dep.name) ?? new Map<string, Set<string>>();
|
||||
const pluginIds = byVersion.get(dep.version) ?? new Set<string>();
|
||||
pluginIds.add(pluginId);
|
||||
byVersion.set(dep.version, pluginIds);
|
||||
versionMap.set(dep.name, byVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3421,6 +3421,78 @@ describe("ensureBundledPluginRuntimeDeps", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("installs local memory embedding runtime deps only when local memory search is configured", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const pluginRoot = writeBundledPluginPackage({
|
||||
packageRoot,
|
||||
pluginId: "memory-core",
|
||||
deps: { chokidar: "^5.0.0", typebox: "1.1.34" },
|
||||
runtimeDependencies: {
|
||||
localMemoryEmbedding: ["node-llama-cpp@3.18.1"],
|
||||
},
|
||||
});
|
||||
const calls: BundledRuntimeDepsInstallParams[] = [];
|
||||
|
||||
const result = ensureBundledPluginRuntimeDeps({
|
||||
env: {},
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: { provider: "local" },
|
||||
},
|
||||
},
|
||||
},
|
||||
installDeps: (params) => {
|
||||
calls.push(params);
|
||||
},
|
||||
pluginId: "memory-core",
|
||||
pluginRoot,
|
||||
});
|
||||
|
||||
expect(result.installedSpecs).toEqual([
|
||||
"chokidar@^5.0.0",
|
||||
"node-llama-cpp@3.18.1",
|
||||
"typebox@1.1.34",
|
||||
]);
|
||||
expect(calls[0]?.installSpecs).toEqual([
|
||||
"chokidar@^5.0.0",
|
||||
"node-llama-cpp@3.18.1",
|
||||
"typebox@1.1.34",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not install local memory embedding runtime deps for remote memory search", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const pluginRoot = writeBundledPluginPackage({
|
||||
packageRoot,
|
||||
pluginId: "memory-core",
|
||||
deps: { chokidar: "^5.0.0", typebox: "1.1.34" },
|
||||
runtimeDependencies: {
|
||||
localMemoryEmbedding: ["node-llama-cpp@3.18.1"],
|
||||
},
|
||||
});
|
||||
const calls: BundledRuntimeDepsInstallParams[] = [];
|
||||
|
||||
const result = ensureBundledPluginRuntimeDeps({
|
||||
env: {},
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: { provider: "openai" },
|
||||
},
|
||||
},
|
||||
},
|
||||
installDeps: (params) => {
|
||||
calls.push(params);
|
||||
},
|
||||
pluginId: "memory-core",
|
||||
pluginRoot,
|
||||
});
|
||||
|
||||
expect(result.installedSpecs).toEqual(["chokidar@^5.0.0", "typebox@1.1.34"]);
|
||||
expect(calls[0]?.installSpecs).toEqual(["chokidar@^5.0.0", "typebox@1.1.34"]);
|
||||
});
|
||||
|
||||
it("repairs external staged deps even when packaged plugin-local deps are present", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const extensionsRoot = path.join(packageRoot, "dist", "extensions");
|
||||
|
||||
@@ -48,6 +48,7 @@ export function writeBundledPluginRuntimeDepsPackage(params: {
|
||||
channels?: string[];
|
||||
modelSupport?: { modelPatterns?: string[]; modelPrefixes?: string[] };
|
||||
providers?: string[];
|
||||
runtimeDependencies?: Record<string, string[]>;
|
||||
}): string {
|
||||
const pluginRoot = path.join(params.packageRoot, "dist", "extensions", params.pluginId);
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
@@ -63,6 +64,7 @@ export function writeBundledPluginRuntimeDepsPackage(params: {
|
||||
...(params.channels ? { channels: params.channels } : {}),
|
||||
...(params.modelSupport ? { modelSupport: params.modelSupport } : {}),
|
||||
...(params.providers ? { providers: params.providers } : {}),
|
||||
...(params.runtimeDependencies ? { runtimeDependencies: params.runtimeDependencies } : {}),
|
||||
}),
|
||||
);
|
||||
return pluginRoot;
|
||||
|
||||
Reference in New Issue
Block a user