fix(memory): keep sqlite-vec optional

This commit is contained in:
Vincent Koc
2026-05-02 23:43:56 -07:00
parent 140c274335
commit 95001d6c41
5 changed files with 64 additions and 9 deletions

View File

@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
- Cron: preserve manual `cron.run` IDs in `cron.runs` history so manual run acknowledgements can be correlated with finished run records. Fixes #76276.
- CLI/devices: request `operator.admin` for `openclaw devices approve <requestId>` only when the exact pending device request would mint or inherit admin-scoped operator access, while keeping lower-scope approvals on the pairing scope.
- Memory/embedding: broaden the embedding reindex retry classifier to include transient socket-layer errors (`fetch failed`, `ECONNRESET`, `socket hang up`, `UND_ERR_*`, `closed`) so memory reindex survives provider network hiccups instead of aborting mid-run. Related #56815, #44166. (#76311) Thanks @buyitsydney.
- Memory/search: keep sqlite-vec optional in packaged installs and point missing-extension recovery at the valid `agents.defaults.memorySearch.store.vector.extensionPath` setting. Thanks @vincentkoc.
- Gateway: keep directly requested plugin tools invokable under restrictive tool profiles while preserving explicit deny lists and the HTTP safety deny list, preventing catalog/invoke mismatches that surface as "Tool not available". Thanks @BunsDev.
- Gateway/update: allow beta binaries to refresh gateway services when the config was last written by the matching stable release version, avoiding false newer-config downgrade blocks during beta channel updates.
- Channels: keep Matrix and Mattermost bundled in the core package instead of advertising external npm installs before those channels are cut over. Thanks @vincentkoc.

View File

@@ -1697,7 +1697,6 @@
"playwright-core": "1.59.1",
"proxy-agent": "^8.0.1",
"qrcode": "1.5.4",
"sqlite-vec": "0.1.9",
"tar": "7.5.13",
"tokenjuice": "0.7.0",
"tree-sitter-bash": "^0.25.1",
@@ -1734,6 +1733,9 @@
"typescript": "^6.0.3",
"vitest": "^4.1.5"
},
"optionalDependencies": {
"sqlite-vec": "0.1.9"
},
"overrides": {
"@aws-sdk/client-bedrock-runtime": "$@aws-sdk/client-bedrock-runtime",
"axios": "1.15.0",

View File

@@ -1,13 +1,26 @@
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("sqlite-vec", () => {
throw new Error("bundled sqlite-vec should not load when extensionPath is explicit");
function mockMissingSqliteVecPackage(): void {
vi.doMock("sqlite-vec", () => {
const err = new Error("Cannot find package 'sqlite-vec' imported from sqlite-vec.test.ts");
Object.assign(err, { code: "ERR_MODULE_NOT_FOUND" });
throw err;
});
}
async function importLoader() {
return import("./sqlite-vec.js");
}
afterEach(() => {
vi.doUnmock("sqlite-vec");
vi.resetModules();
});
import { loadSqliteVecExtension } from "./sqlite-vec.js";
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(),
@@ -22,4 +35,23 @@ describe("loadSqliteVecExtension", () => {
expect(db.enableLoadExtension).toHaveBeenCalledWith(true);
expect(db.loadExtension).toHaveBeenCalledWith("/opt/openclaw/sqlite-vec.so");
});
it("returns a valid memorySearch extensionPath hint when sqlite-vec is absent", async () => {
mockMissingSqliteVecPackage();
const { loadSqliteVecExtension } = await importLoader();
const db = {
enableLoadExtension: vi.fn(),
loadExtension: vi.fn(),
};
const result = await loadSqliteVecExtension({ db: db as never });
expect(result.ok).toBe(false);
expect(result.error).toContain("sqlite-vec package is not installed.");
expect(result.error).toContain("agents.defaults.memorySearch.store.vector.extensionPath");
expect(result.error).toContain("agent-specific memorySearch.store.vector.extensionPath");
expect(result.error).not.toContain("memory.store.vector.extensionPath");
expect(db.enableLoadExtension).toHaveBeenCalledWith(true);
expect(db.loadExtension).not.toHaveBeenCalled();
});
});

View File

@@ -8,11 +8,24 @@ type SqliteVecModule = {
};
const SQLITE_VEC_MODULE_ID = "sqlite-vec";
const SQLITE_VEC_CONFIG_HINT =
"Set agents.defaults.memorySearch.store.vector.extensionPath, or an agent-specific memorySearch.store.vector.extensionPath, to a sqlite-vec loadable extension path.";
async function loadSqliteVecModule(): Promise<SqliteVecModule> {
return import(SQLITE_VEC_MODULE_ID) as Promise<SqliteVecModule>;
}
function isMissingSqliteVecPackageError(err: unknown): boolean {
const message = formatErrorMessage(err);
const code =
err && typeof err === "object" && "code" in err ? (err as { code?: unknown }).code : undefined;
const missingSqliteVec = /Cannot find (?:package|module) ['"]sqlite-vec['"]/u.test(message);
return (
missingSqliteVec &&
(code === undefined || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND")
);
}
export async function loadSqliteVecExtension(params: {
db: DatabaseSync;
extensionPath?: string;
@@ -31,6 +44,12 @@ export async function loadSqliteVecExtension(params: {
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}`,
};
}
return { ok: false, error: message };
}
}

7
pnpm-lock.yaml generated
View File

@@ -184,9 +184,6 @@ importers:
qrcode:
specifier: 1.5.4
version: 1.5.4
sqlite-vec:
specifier: 0.1.9
version: 0.1.9
tar:
specifier: 7.5.13
version: 7.5.13
@@ -220,6 +217,10 @@ importers:
zod:
specifier: ^4.4.1
version: 4.4.1
optionalDependencies:
sqlite-vec:
specifier: 0.1.9
version: 0.1.9
devDependencies:
'@copilotkit/aimock':
specifier: 1.16.4