mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix(memory): load sqlite-vec platform variant fallback
Resolve the sqlite-vec platform package exported native extension when the meta package is absent, preserving explicit extensionPath priority and keeping the existing config hint on load failures. Adds coverage for the real exported vec0 subpath so package.json export-map regressions fail in tests. Fixes #77838. Co-authored-by: corevibe555 <leaderbossprog2025@gmail.com>
This commit is contained in:
committed by
Peter Steinberger
parent
3bad58e3eb
commit
a1fc955aef
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Bonjour/Gateway: treat active ciao probing and fresh name-conflict renames as in-progress so the mDNS watchdog waits for probe settlement before retrying, preventing rapid re-advertise loops on Windows, WSL, and other multicast-hostile hosts. (#74778) Refs #74242. Thanks @fuller-stack-dev.
|
||||
- Providers/MiniMax: send a minimal Anthropic-compatible user fallback when message conversion filters a turn to an empty payload, so MiniMax M2.7 no longer returns `chat content is empty` after tool-heavy sessions. Fixes #74589. Thanks @neeravmakwana and @DerekEXS.
|
||||
- Tools/media: preserve implicit allow-all semantics from `tools.alsoAllow`-only policies when preconstructing built-in media generation and PDF tools, so configured media tools become live without forcing `tools.allow: ["*", ...]`. Fixes #77841. Thanks @trialanderrorstudios.
|
||||
- Memory/search: load the platform-specific `sqlite-vec-<platform>-<arch>` variant directly when the meta `sqlite-vec` package is missing from a global install, so vector recall keeps working on `npm install -g openclaw@latest` upgrades where optionalDependencies left only the platform variant on disk. Fixes #77838. Thanks @corevibe555 and @Simon2256928.
|
||||
- Cron: keep long manual cron runs active in the task registry until completion, preventing transient `lost` markers before durable recovery reconciles. Fixes #78233. (#78243) Thanks @Feelw00.
|
||||
- Doctor/GitHub CLI: surface a `GH_CONFIG_DIR` hint when the GitHub skill is usable but `gh` auth lives under a different operator HOME than the agent process, without warning for disabled or filtered skills. Fixes #78063. (#78095) Thanks @tmimmanuel.
|
||||
- Gateway: dedupe concurrent `send`, `poll`, and `message.action` requests while delivery is still in flight, preventing duplicate outbound work for the same idempotency key. (#68341) Thanks @thesomewhatyou.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
type PlatformVariant = { readonly pkg: string; readonly file: string };
|
||||
|
||||
const PLATFORM_VARIANTS: Readonly<Record<string, PlatformVariant | undefined>> = {
|
||||
"linux-x64": { pkg: "sqlite-vec-linux-x64", file: "vec0.so" },
|
||||
"linux-arm64": { pkg: "sqlite-vec-linux-arm64", file: "vec0.so" },
|
||||
"darwin-x64": { pkg: "sqlite-vec-darwin-x64", file: "vec0.dylib" },
|
||||
"darwin-arm64": { pkg: "sqlite-vec-darwin-arm64", file: "vec0.dylib" },
|
||||
"win32-x64": { pkg: "sqlite-vec-windows-x64", file: "vec0.dll" },
|
||||
};
|
||||
|
||||
export function resolveSqliteVecPlatformVariant():
|
||||
| { pkg: string; extensionPath: string }
|
||||
| undefined {
|
||||
const entry = PLATFORM_VARIANTS[`${process.platform}-${process.arch}`];
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const require_ = createRequire(import.meta.url);
|
||||
const extensionPath = require_.resolve(`${entry.pkg}/${entry.file}`);
|
||||
return { pkg: entry.pkg, extensionPath };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { loadSqliteVecExtension } from "./sqlite-vec.js";
|
||||
|
||||
function mockMissingSqliteVecPackage(): void {
|
||||
vi.doMock("sqlite-vec", () => {
|
||||
@@ -9,13 +10,44 @@ function mockMissingSqliteVecPackage(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function mockPlatformVariantResolver(
|
||||
value: { pkg: string; extensionPath: string } | undefined,
|
||||
): void {
|
||||
vi.doMock("./sqlite-vec-platform-variant.js", () => ({
|
||||
resolveSqliteVecPlatformVariant: () => value,
|
||||
}));
|
||||
}
|
||||
|
||||
async function importLoader() {
|
||||
return import("./sqlite-vec.js");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.doUnmock("sqlite-vec");
|
||||
vi.doUnmock("./sqlite-vec-platform-variant.js");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
const CURRENT_PLATFORM_VARIANTS: Readonly<
|
||||
Record<string, { readonly pkg: string; readonly file: string } | undefined>
|
||||
> = {
|
||||
"linux-x64": { pkg: "sqlite-vec-linux-x64", file: "vec0.so" },
|
||||
"linux-arm64": { pkg: "sqlite-vec-linux-arm64", file: "vec0.so" },
|
||||
"darwin-x64": { pkg: "sqlite-vec-darwin-x64", file: "vec0.dylib" },
|
||||
"darwin-arm64": { pkg: "sqlite-vec-darwin-arm64", file: "vec0.dylib" },
|
||||
"win32-x64": { pkg: "sqlite-vec-windows-x64", file: "vec0.dll" },
|
||||
};
|
||||
|
||||
function isMissingModuleError(err: unknown): boolean {
|
||||
const code =
|
||||
err && typeof err === "object" && "code" in err ? (err as { code?: unknown }).code : undefined;
|
||||
return code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
|
||||
}
|
||||
|
||||
describe("loadSqliteVecExtension", () => {
|
||||
it("loads explicit extensionPath without importing bundled sqlite-vec", async () => {
|
||||
mockMissingSqliteVecPackage();
|
||||
const { loadSqliteVecExtension } = await importLoader();
|
||||
const db = {
|
||||
enableLoadExtension: vi.fn(),
|
||||
loadExtension: vi.fn(),
|
||||
@@ -33,6 +65,8 @@ describe("loadSqliteVecExtension", () => {
|
||||
|
||||
it("returns a valid memorySearch extensionPath hint when sqlite-vec is absent", async () => {
|
||||
mockMissingSqliteVecPackage();
|
||||
mockPlatformVariantResolver(undefined);
|
||||
const { loadSqliteVecExtension } = await importLoader();
|
||||
const db = {
|
||||
enableLoadExtension: vi.fn(),
|
||||
loadExtension: vi.fn(),
|
||||
@@ -48,4 +82,76 @@ describe("loadSqliteVecExtension", () => {
|
||||
expect(db.enableLoadExtension).toHaveBeenCalledWith(true);
|
||||
expect(db.loadExtension).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the platform-specific sqlite-vec variant when only that package is installed", async () => {
|
||||
mockMissingSqliteVecPackage();
|
||||
mockPlatformVariantResolver({
|
||||
pkg: "sqlite-vec-linux-x64",
|
||||
extensionPath: "/install/node_modules/sqlite-vec-linux-x64/vec0.so",
|
||||
});
|
||||
const { loadSqliteVecExtension } = await importLoader();
|
||||
const db = {
|
||||
enableLoadExtension: vi.fn(),
|
||||
loadExtension: vi.fn(),
|
||||
};
|
||||
|
||||
const result = await loadSqliteVecExtension({ db: db as never });
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
extensionPath: "/install/node_modules/sqlite-vec-linux-x64/vec0.so",
|
||||
});
|
||||
expect(db.enableLoadExtension).toHaveBeenCalledWith(true);
|
||||
expect(db.loadExtension).toHaveBeenCalledWith(
|
||||
"/install/node_modules/sqlite-vec-linux-x64/vec0.so",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves the installed platform variant through its exported vec0 subpath", async () => {
|
||||
const entry = CURRENT_PLATFORM_VARIANTS[`${process.platform}-${process.arch}`];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const require_ = createRequire(import.meta.url);
|
||||
let expectedPath: string;
|
||||
try {
|
||||
expectedPath = require_.resolve(`${entry.pkg}/${entry.file}`);
|
||||
} catch (err) {
|
||||
if (isMissingModuleError(err)) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { resolveSqliteVecPlatformVariant } = await import("./sqlite-vec-platform-variant.js");
|
||||
|
||||
expect(resolveSqliteVecPlatformVariant()).toEqual({
|
||||
pkg: entry.pkg,
|
||||
extensionPath: expectedPath,
|
||||
});
|
||||
expect(existsSync(expectedPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves the extensionPath config hint when the platform variant loadExtension call throws", async () => {
|
||||
mockMissingSqliteVecPackage();
|
||||
mockPlatformVariantResolver({
|
||||
pkg: "sqlite-vec-linux-x64",
|
||||
extensionPath: "/install/node_modules/sqlite-vec-linux-x64/vec0.so",
|
||||
});
|
||||
const { loadSqliteVecExtension } = await importLoader();
|
||||
const db = {
|
||||
enableLoadExtension: vi.fn(),
|
||||
loadExtension: vi.fn().mockImplementation(() => {
|
||||
throw new Error("dlopen failed: file not found");
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await loadSqliteVecExtension({ db: db as never });
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("sqlite-vec-linux-x64");
|
||||
expect(result.error).toContain("agents.defaults.memorySearch.store.vector.extensionPath");
|
||||
expect(result.error).toContain("dlopen failed: file not found");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { formatErrorMessage } from "./error-utils.js";
|
||||
import { resolveSqliteVecPlatformVariant } from "./sqlite-vec-platform-variant.js";
|
||||
import { normalizeOptionalString } from "./string-utils.js";
|
||||
|
||||
type SqliteVecModule = {
|
||||
@@ -38,18 +39,38 @@ export async function loadSqliteVecExtension(params: {
|
||||
return { ok: true, extensionPath: resolvedPath };
|
||||
}
|
||||
|
||||
const sqliteVec = await loadSqliteVecModule();
|
||||
const extensionPath = sqliteVec.getLoadablePath();
|
||||
sqliteVec.load(params.db);
|
||||
return { ok: true, extensionPath };
|
||||
} catch (err) {
|
||||
const message = formatErrorMessage(err);
|
||||
if (isMissingSqliteVecPackageError(err)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `sqlite-vec package is not installed. ${SQLITE_VEC_CONFIG_HINT} Original error: ${message}`,
|
||||
};
|
||||
try {
|
||||
const sqliteVec = await loadSqliteVecModule();
|
||||
const extensionPath = sqliteVec.getLoadablePath();
|
||||
sqliteVec.load(params.db);
|
||||
return { ok: true, extensionPath };
|
||||
} catch (err) {
|
||||
if (!isMissingSqliteVecPackageError(err)) {
|
||||
throw err;
|
||||
}
|
||||
// Optional-dep installs sometimes land only the platform-specific variant
|
||||
// (e.g. sqlite-vec-linux-x64) without the meta sqlite-vec package. Load
|
||||
// the loadable extension straight from the variant when we can find it.
|
||||
const variant = resolveSqliteVecPlatformVariant();
|
||||
if (!variant) {
|
||||
const message = formatErrorMessage(err);
|
||||
return {
|
||||
ok: false,
|
||||
error: `sqlite-vec package is not installed. ${SQLITE_VEC_CONFIG_HINT} Original error: ${message}`,
|
||||
};
|
||||
}
|
||||
try {
|
||||
params.db.loadExtension(variant.extensionPath);
|
||||
return { ok: true, extensionPath: variant.extensionPath };
|
||||
} catch (variantErr) {
|
||||
const message = formatErrorMessage(variantErr);
|
||||
return {
|
||||
ok: false,
|
||||
error: `sqlite-vec platform variant ${variant.pkg} failed to load from ${variant.extensionPath}. ${SQLITE_VEC_CONFIG_HINT} Original error: ${message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { ok: false, error: message };
|
||||
} catch (err) {
|
||||
return { ok: false, error: formatErrorMessage(err) };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user