@openclaw/memory-lancedb 2026.7.2-beta.7 → 2026.7.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-metadata.js +1 -3
- package/dist/config.js +18 -2
- package/dist/index.js +1098 -280
- package/dist/lancedb-runtime.js +1 -2
- package/openclaw.plugin.json +0 -1
- package/package.json +7 -7
- package/dist/cli-output-mode.js +0 -13
- package/dist/doctor-contract-api.js +0 -179
- package/dist/embeddings.js +0 -312
- package/dist/lancedb-schema.js +0 -17
- package/dist/lancedb-store.js +0 -169
- package/dist/memory-capture-sanitization.js +0 -346
- package/dist/memory-cli.js +0 -134
- package/dist/memory-policy.js +0 -145
package/dist/lancedb-runtime.js
CHANGED
|
@@ -38,10 +38,9 @@ function createLanceDbRuntimeLoader(overrides = {}) {
|
|
|
38
38
|
return await loadPromise;
|
|
39
39
|
} };
|
|
40
40
|
}
|
|
41
|
-
if (process.env.VITEST === "true") Reflect.set(globalThis, Symbol.for("openclaw.memoryLanceDbRuntimeTestApi"), { createRuntimeLoader: createLanceDbRuntimeLoader });
|
|
42
41
|
const defaultLoader = createLanceDbRuntimeLoader();
|
|
43
42
|
async function loadLanceDbModule(logger) {
|
|
44
43
|
return await defaultLoader.load(logger);
|
|
45
44
|
}
|
|
46
45
|
//#endregion
|
|
47
|
-
export { loadLanceDbModule };
|
|
46
|
+
export { createLanceDbRuntimeLoader, loadLanceDbModule };
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"id": "memory-lancedb",
|
|
3
3
|
"name": "Memory LanceDB",
|
|
4
4
|
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search.",
|
|
5
|
-
"catalog": { "featured": true, "order": 70 },
|
|
6
5
|
"commandAliases": [{ "name": "ltm" }],
|
|
7
6
|
"activation": {
|
|
8
7
|
"onStartup": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/memory-lancedb",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.33",
|
|
4
4
|
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@lancedb/lancedb": "0.
|
|
11
|
+
"@lancedb/lancedb": "0.30.0",
|
|
12
12
|
"apache-arrow": "18.1.0",
|
|
13
|
-
"openai": "6.
|
|
14
|
-
"typebox": "1.3.
|
|
13
|
+
"openai": "6.45.0",
|
|
14
|
+
"typebox": "1.3.3"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@openclaw/plugin-sdk": "workspace:*"
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"minHostVersion": ">=2026.5.31"
|
|
27
27
|
},
|
|
28
28
|
"compat": {
|
|
29
|
-
"pluginApi": ">=2026.7.
|
|
29
|
+
"pluginApi": ">=2026.7.33"
|
|
30
30
|
},
|
|
31
31
|
"build": {
|
|
32
|
-
"openclawVersion": "2026.7.
|
|
32
|
+
"openclawVersion": "2026.7.33"
|
|
33
33
|
},
|
|
34
34
|
"release": {
|
|
35
35
|
"bundleRuntimeDependencies": false,
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"README.md"
|
|
47
47
|
],
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"openclaw": ">=2026.7.
|
|
49
|
+
"openclaw": ">=2026.7.33"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
52
52
|
"openclaw": {
|
package/dist/cli-output-mode.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getRootOptionAwareCommandPath } from "openclaw/plugin-sdk/cli-argv";
|
|
2
|
-
//#region extensions/memory-lancedb/cli-output-mode.ts
|
|
3
|
-
/** LanceDB inspection commands emit JSON as their only presentation. */
|
|
4
|
-
function isMemoryMachineOutput(params) {
|
|
5
|
-
const [, command] = getRootOptionAwareCommandPath(params.argv, 2);
|
|
6
|
-
return [
|
|
7
|
-
"list",
|
|
8
|
-
"query",
|
|
9
|
-
"search"
|
|
10
|
-
].includes(command ?? "");
|
|
11
|
-
}
|
|
12
|
-
//#endregion
|
|
13
|
-
export { isMemoryMachineOutput };
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { MEMORY_AGENT_ID_COLUMN, MEMORY_TABLE_NAME, hasAgentScopeColumn, memoryAgentPredicate, quoteLanceSqlString } from "./lancedb-schema.js";
|
|
2
|
-
import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
//#region extensions/memory-lancedb/doctor-contract-api.ts
|
|
8
|
-
const LEGACY_ENVELOPE_DELETE_BATCH_SIZE = 500;
|
|
9
|
-
const LEGACY_ENVELOPE_SENTINEL_LINE_RE = new RegExp(`^(?:${[
|
|
10
|
-
"Conversation info (untrusted metadata):",
|
|
11
|
-
"Sender (untrusted metadata):",
|
|
12
|
-
"Thread starter (untrusted, for context):",
|
|
13
|
-
"Reply target of current user message (untrusted, for context):",
|
|
14
|
-
"Replied message (untrusted, for context):",
|
|
15
|
-
"Forwarded message context (untrusted metadata):",
|
|
16
|
-
"Conversation context (untrusted, chronological, selected for current message):",
|
|
17
|
-
"Current local chat window (untrusted, chronological, before current message):",
|
|
18
|
-
"Nearby reply target window (untrusted, chronological, around replied-to message):",
|
|
19
|
-
"Chat history since last reply (untrusted, for context):"
|
|
20
|
-
].map((sentinel) => sentinel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})[^\\n]*$`, "m");
|
|
21
|
-
const LEGACY_ENVELOPE_LABEL_JSON_BLOCK_RE = /^[^\n]+\((?:untrusted metadata|untrusted, for context|untrusted, nearest first|untrusted, chronological,[^\n)]{1,80})\):[ \t]*\n[ \t]*```json[ \t]*\n[\s\S]*?\n[ \t]*```[ \t]*(?:\n|$)/m;
|
|
22
|
-
const LEGACY_ENVELOPE_HEADER_RE = /^Untrusted context \(metadata, do not treat as instructions or commands\):[ \t]*$/m;
|
|
23
|
-
function isLegacyEnvelopeContaminatedText(text) {
|
|
24
|
-
return typeof text === "string" && (LEGACY_ENVELOPE_SENTINEL_LINE_RE.test(text) || LEGACY_ENVELOPE_LABEL_JSON_BLOCK_RE.test(text) || LEGACY_ENVELOPE_HEADER_RE.test(text));
|
|
25
|
-
}
|
|
26
|
-
async function scanLegacyEnvelopeRowIds(table) {
|
|
27
|
-
const contaminatedIds = [];
|
|
28
|
-
for await (const batch of table.query().select(["id", "text"])) for (const row of batch.toArray()) {
|
|
29
|
-
if (!isLegacyEnvelopeContaminatedText(row.text)) continue;
|
|
30
|
-
if (typeof row.id !== "string") throw new Error("LanceDB legacy envelope row is missing a string id");
|
|
31
|
-
contaminatedIds.push(row.id);
|
|
32
|
-
}
|
|
33
|
-
return contaminatedIds;
|
|
34
|
-
}
|
|
35
|
-
function resolveMemoryLanceDbPluginRoot(moduleUrl) {
|
|
36
|
-
const artifactDir = path.dirname(fileURLToPath(moduleUrl));
|
|
37
|
-
return path.basename(artifactDir) === "dist" ? path.dirname(artifactDir) : artifactDir;
|
|
38
|
-
}
|
|
39
|
-
const DEFAULT_PLUGIN_ROOT = resolveMemoryLanceDbPluginRoot(import.meta.url);
|
|
40
|
-
function asRecord(value) {
|
|
41
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
42
|
-
}
|
|
43
|
-
function resolveHome(env) {
|
|
44
|
-
return env.HOME?.trim() || os.homedir();
|
|
45
|
-
}
|
|
46
|
-
function resolveConfiguredDbPath(config, env, pluginRoot) {
|
|
47
|
-
const pluginConfig = asRecord(config.plugins?.entries?.["memory-lancedb"]?.config);
|
|
48
|
-
const configured = typeof pluginConfig?.dbPath === "string" ? pluginConfig.dbPath.trim() : "";
|
|
49
|
-
if (!configured) return path.join(resolveHome(env), ".openclaw", "memory", "lancedb");
|
|
50
|
-
if (configured.includes("://")) return configured;
|
|
51
|
-
if (configured.startsWith("~")) return path.resolve(configured.replace(/^~(?=$|[\\/])/, resolveHome(env)));
|
|
52
|
-
return path.resolve(pluginRoot, configured);
|
|
53
|
-
}
|
|
54
|
-
function resolveStorageOptions(config, env) {
|
|
55
|
-
const rawOptions = asRecord(asRecord(config.plugins?.entries?.["memory-lancedb"]?.config)?.storageOptions);
|
|
56
|
-
if (!rawOptions) return;
|
|
57
|
-
return Object.fromEntries(Object.entries(rawOptions).map(([key, value]) => {
|
|
58
|
-
if (typeof value !== "string") throw new Error(`memory-lancedb storageOptions.${key} must be a string`);
|
|
59
|
-
return [key, value.replace(/\$\{([^}]+)\}/g, (_match, envName) => {
|
|
60
|
-
const resolved = env[envName];
|
|
61
|
-
if (!resolved) throw new Error(`Environment variable ${envName} is not set`);
|
|
62
|
-
return resolved;
|
|
63
|
-
})];
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
async function openMemoryTable(params) {
|
|
67
|
-
const dbPath = resolveConfiguredDbPath(params.config, params.env, params.pluginRoot);
|
|
68
|
-
if (!dbPath.includes("://") && !fs.existsSync(dbPath)) return {
|
|
69
|
-
connection: null,
|
|
70
|
-
table: null,
|
|
71
|
-
dbPath
|
|
72
|
-
};
|
|
73
|
-
const lancedb = await import("@lancedb/lancedb");
|
|
74
|
-
const storageOptions = resolveStorageOptions(params.config, params.env);
|
|
75
|
-
const connection = await lancedb.connect(dbPath, storageOptions ? { storageOptions } : {});
|
|
76
|
-
return {
|
|
77
|
-
connection,
|
|
78
|
-
table: (await connection.tableNames()).includes("memories") ? await connection.openTable(MEMORY_TABLE_NAME) : null,
|
|
79
|
-
dbPath
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function createMemoryLanceDbStateMigrations(pluginRoot = DEFAULT_PLUGIN_ROOT) {
|
|
83
|
-
return [{
|
|
84
|
-
id: "memory-lancedb-agent-scope",
|
|
85
|
-
label: "Memory LanceDB per-agent isolation",
|
|
86
|
-
async detectLegacyState(params) {
|
|
87
|
-
const opened = await openMemoryTable({
|
|
88
|
-
...params,
|
|
89
|
-
pluginRoot
|
|
90
|
-
});
|
|
91
|
-
try {
|
|
92
|
-
if (!opened.table || hasAgentScopeColumn(await opened.table.schema())) return null;
|
|
93
|
-
const defaultAgentId = resolveDefaultAgentId(params.config);
|
|
94
|
-
const count = await opened.table.countRows();
|
|
95
|
-
return { preview: [`- Memory LanceDB: assign ${count} legacy ${count === 1 ? "row" : "rows"} at ${opened.dbPath} to default agent ${defaultAgentId}`] };
|
|
96
|
-
} finally {
|
|
97
|
-
opened.table?.close();
|
|
98
|
-
opened.connection?.close();
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
async migrateLegacyState(params) {
|
|
102
|
-
const opened = await openMemoryTable({
|
|
103
|
-
...params,
|
|
104
|
-
pluginRoot
|
|
105
|
-
});
|
|
106
|
-
try {
|
|
107
|
-
if (!opened.table || hasAgentScopeColumn(await opened.table.schema())) return {
|
|
108
|
-
changes: [],
|
|
109
|
-
warnings: []
|
|
110
|
-
};
|
|
111
|
-
const defaultAgentId = resolveDefaultAgentId(params.config);
|
|
112
|
-
const rowCount = await opened.table.countRows();
|
|
113
|
-
await opened.table.addColumns([{
|
|
114
|
-
name: MEMORY_AGENT_ID_COLUMN,
|
|
115
|
-
valueSql: quoteLanceSqlString(defaultAgentId)
|
|
116
|
-
}]);
|
|
117
|
-
if (!hasAgentScopeColumn(await opened.table.schema()) || await opened.table.countRows(memoryAgentPredicate(defaultAgentId)) !== rowCount) throw new Error("LanceDB agent-scope migration verification failed");
|
|
118
|
-
return {
|
|
119
|
-
changes: [`Assigned ${rowCount} legacy Memory LanceDB ${rowCount === 1 ? "row" : "rows"} to default agent ${defaultAgentId}`],
|
|
120
|
-
warnings: []
|
|
121
|
-
};
|
|
122
|
-
} finally {
|
|
123
|
-
opened.table?.close();
|
|
124
|
-
opened.connection?.close();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}, {
|
|
128
|
-
id: "memory-lancedb-legacy-envelope-rows",
|
|
129
|
-
label: "Memory LanceDB legacy envelope contamination",
|
|
130
|
-
doctorOnly: true,
|
|
131
|
-
async detectLegacyState(params) {
|
|
132
|
-
const opened = await openMemoryTable({
|
|
133
|
-
...params,
|
|
134
|
-
pluginRoot
|
|
135
|
-
});
|
|
136
|
-
try {
|
|
137
|
-
if (!opened.table) return null;
|
|
138
|
-
const contaminatedIds = await scanLegacyEnvelopeRowIds(opened.table);
|
|
139
|
-
if (contaminatedIds.length === 0) return null;
|
|
140
|
-
return { preview: [`- Memory LanceDB: delete ${contaminatedIds.length} memory ${contaminatedIds.length === 1 ? "row" : "rows"} contaminated with legacy envelope metadata at ${opened.dbPath}`] };
|
|
141
|
-
} finally {
|
|
142
|
-
opened.table?.close();
|
|
143
|
-
opened.connection?.close();
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
async migrateLegacyState(params) {
|
|
147
|
-
const opened = await openMemoryTable({
|
|
148
|
-
...params,
|
|
149
|
-
pluginRoot
|
|
150
|
-
});
|
|
151
|
-
try {
|
|
152
|
-
if (!opened.table) return {
|
|
153
|
-
changes: [],
|
|
154
|
-
warnings: []
|
|
155
|
-
};
|
|
156
|
-
const contaminatedIds = await scanLegacyEnvelopeRowIds(opened.table);
|
|
157
|
-
if (contaminatedIds.length === 0) return {
|
|
158
|
-
changes: [],
|
|
159
|
-
warnings: []
|
|
160
|
-
};
|
|
161
|
-
for (let offset = 0; offset < contaminatedIds.length; offset += LEGACY_ENVELOPE_DELETE_BATCH_SIZE) {
|
|
162
|
-
const batch = contaminatedIds.slice(offset, offset + LEGACY_ENVELOPE_DELETE_BATCH_SIZE);
|
|
163
|
-
await opened.table.delete(`id IN (${batch.map((id) => quoteLanceSqlString(id)).join(", ")})`);
|
|
164
|
-
}
|
|
165
|
-
if ((await scanLegacyEnvelopeRowIds(opened.table)).length !== 0) throw new Error("LanceDB legacy envelope row migration verification failed");
|
|
166
|
-
return {
|
|
167
|
-
changes: [`Deleted ${contaminatedIds.length} Memory LanceDB ${contaminatedIds.length === 1 ? "row" : "rows"} contaminated with legacy envelope metadata`],
|
|
168
|
-
warnings: []
|
|
169
|
-
};
|
|
170
|
-
} finally {
|
|
171
|
-
opened.table?.close();
|
|
172
|
-
opened.connection?.close();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}];
|
|
176
|
-
}
|
|
177
|
-
const stateMigrations = createMemoryLanceDbStateMigrations();
|
|
178
|
-
//#endregion
|
|
179
|
-
export { createMemoryLanceDbStateMigrations, resolveMemoryLanceDbPluginRoot, stateMigrations };
|
package/dist/embeddings.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
2
|
-
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
-
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
4
|
-
import { Buffer } from "node:buffer";
|
|
5
|
-
import { toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
|
6
|
-
import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
|
|
7
|
-
import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
|
|
8
|
-
import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env";
|
|
9
|
-
//#region extensions/memory-lancedb/embeddings.ts
|
|
10
|
-
const loadOpenAiModule = createLazyRuntimeModule(() => import("openai"));
|
|
11
|
-
const loadMemoryEmbeddingProviderModule = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/memory-core-host-engine-embeddings"));
|
|
12
|
-
const loadMemoryHostCoreModule = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/memory-host-core"));
|
|
13
|
-
const PROVIDER_ADAPTER_LIFECYCLE = resolveGlobalSingleton(Symbol.for("openclaw.memoryLanceDbEmbeddingProviderLifecycle.v1"), () => ({
|
|
14
|
-
retainedProviders: /* @__PURE__ */ new Set(),
|
|
15
|
-
tail: Promise.resolve()
|
|
16
|
-
}));
|
|
17
|
-
function runProviderAdapterLifecycle(operation) {
|
|
18
|
-
const result = PROVIDER_ADAPTER_LIFECYCLE.tail.then(operation, operation);
|
|
19
|
-
PROVIDER_ADAPTER_LIFECYCLE.tail = result.then(() => void 0, () => void 0);
|
|
20
|
-
return result;
|
|
21
|
-
}
|
|
22
|
-
async function drainRetainedProviders() {
|
|
23
|
-
let firstError;
|
|
24
|
-
let closeFailed = false;
|
|
25
|
-
for (const provider of PROVIDER_ADAPTER_LIFECYCLE.retainedProviders) try {
|
|
26
|
-
await provider.close?.();
|
|
27
|
-
PROVIDER_ADAPTER_LIFECYCLE.retainedProviders.delete(provider);
|
|
28
|
-
} catch (err) {
|
|
29
|
-
if (!closeFailed) firstError = err;
|
|
30
|
-
closeFailed = true;
|
|
31
|
-
}
|
|
32
|
-
if (closeFailed) throw toErrorObject(firstError, "memory-lancedb embedding provider retirement failed");
|
|
33
|
-
}
|
|
34
|
-
var OpenAiCompatibleEmbeddings = class {
|
|
35
|
-
constructor(apiKey, model, baseUrl, dimensions) {
|
|
36
|
-
this.model = model;
|
|
37
|
-
this.dimensions = dimensions;
|
|
38
|
-
this.clientPromise = loadOpenAiModule().then(({ default: OpenAI }) => new OpenAI({
|
|
39
|
-
apiKey,
|
|
40
|
-
baseURL: baseUrl
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
async embed(text, options) {
|
|
44
|
-
const dimensions = this.dimensions;
|
|
45
|
-
const startedAtMs = options?.timeoutMs && Number.isFinite(options.timeoutMs) ? Date.now() : null;
|
|
46
|
-
try {
|
|
47
|
-
return normalizeEmbeddingVector((await this.postEmbedding(text, {
|
|
48
|
-
includeDimensions: true,
|
|
49
|
-
options
|
|
50
|
-
})).data?.[0]?.embedding);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if (typeof dimensions !== "number" || !isEmbeddingDimensionsRejectedError(error)) throw error;
|
|
53
|
-
}
|
|
54
|
-
const fallbackOptions = startedAtMs === null || options?.timeoutMs === void 0 ? options : { timeoutMs: Math.max(1, options.timeoutMs - (Date.now() - startedAtMs)) };
|
|
55
|
-
return truncateEmbeddingVector(normalizeEmbeddingVector((await this.postEmbedding(text, {
|
|
56
|
-
includeDimensions: false,
|
|
57
|
-
options: fallbackOptions
|
|
58
|
-
})).data?.[0]?.embedding), dimensions, this.model);
|
|
59
|
-
}
|
|
60
|
-
async postEmbedding(text, request) {
|
|
61
|
-
const params = {
|
|
62
|
-
model: this.model,
|
|
63
|
-
input: text,
|
|
64
|
-
...request.includeDimensions && typeof this.dimensions === "number" ? { dimensions: this.dimensions } : {}
|
|
65
|
-
};
|
|
66
|
-
ensureGlobalUndiciEnvProxyDispatcher();
|
|
67
|
-
return await (await this.clientPromise).post("/embeddings", {
|
|
68
|
-
body: params,
|
|
69
|
-
...request.options?.timeoutMs ? {
|
|
70
|
-
timeout: request.options.timeoutMs,
|
|
71
|
-
maxRetries: 0
|
|
72
|
-
} : {}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
function isEmbeddingDimensionsRejectedError(error) {
|
|
77
|
-
const record = asOptionalRecord(error);
|
|
78
|
-
if (record?.status !== 400 && record?.status !== 422) return false;
|
|
79
|
-
const details = stringifyEmbeddingApiError(error).toLowerCase();
|
|
80
|
-
return /\bdimensions\b/.test(details) && isUnsupportedEmbeddingFieldError(details);
|
|
81
|
-
}
|
|
82
|
-
function isUnsupportedEmbeddingFieldError(details) {
|
|
83
|
-
if (/\b(?:parameter|field|argument)[_ -]value\b/.test(details)) return false;
|
|
84
|
-
return /\bextra[_ -]forbidden\b/.test(details) || /\bextra inputs? (?:are )?not permitted\b/.test(details) || /\bextra fields? (?:are )?not permitted\b/.test(details) || /\b(?:unknown|unrecognized|unexpected|unsupported)[_ -](?:request[_ -])?(?:parameter|field|argument)\b/.test(details);
|
|
85
|
-
}
|
|
86
|
-
function stringifyEmbeddingApiError(error) {
|
|
87
|
-
const record = asOptionalRecord(error);
|
|
88
|
-
const parts = error instanceof Error ? [error.message] : [];
|
|
89
|
-
for (const value of [
|
|
90
|
-
record?.code,
|
|
91
|
-
record?.type,
|
|
92
|
-
record?.param,
|
|
93
|
-
record?.error
|
|
94
|
-
]) {
|
|
95
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
96
|
-
parts.push(String(value));
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (value && typeof value === "object") try {
|
|
100
|
-
parts.push(JSON.stringify(value));
|
|
101
|
-
} catch {}
|
|
102
|
-
}
|
|
103
|
-
return parts.join("\n");
|
|
104
|
-
}
|
|
105
|
-
function truncateEmbeddingVector(embedding, dimensions, model) {
|
|
106
|
-
if (embedding.length < dimensions) throw new Error(`Embedding model ${model} returned ${embedding.length} dimensions, need at least ${dimensions} for local truncation`);
|
|
107
|
-
const truncated = embedding.slice(0, dimensions);
|
|
108
|
-
const magnitude = Math.sqrt(truncated.reduce((sum, value) => sum + value * value, 0));
|
|
109
|
-
return magnitude > 0 ? truncated.map((value) => value / magnitude) : truncated;
|
|
110
|
-
}
|
|
111
|
-
var ProviderAdapterEmbeddings = class {
|
|
112
|
-
constructor(api, embedding) {
|
|
113
|
-
this.api = api;
|
|
114
|
-
this.embedding = embedding;
|
|
115
|
-
this.closePromise = null;
|
|
116
|
-
this.closed = false;
|
|
117
|
-
this.activeUses = 0;
|
|
118
|
-
this.idleWaiters = /* @__PURE__ */ new Set();
|
|
119
|
-
}
|
|
120
|
-
getProvider() {
|
|
121
|
-
this.providerPromise ??= this.createProvider().catch((err) => {
|
|
122
|
-
this.providerPromise = void 0;
|
|
123
|
-
throw err;
|
|
124
|
-
});
|
|
125
|
-
return this.providerPromise;
|
|
126
|
-
}
|
|
127
|
-
acquireUse() {
|
|
128
|
-
if (this.closed) throw new Error("memory-lancedb embeddings are closed");
|
|
129
|
-
this.activeUses += 1;
|
|
130
|
-
let released = false;
|
|
131
|
-
return () => {
|
|
132
|
-
if (released) return;
|
|
133
|
-
released = true;
|
|
134
|
-
this.activeUses -= 1;
|
|
135
|
-
if (this.activeUses === 0) {
|
|
136
|
-
const waiters = Array.from(this.idleWaiters);
|
|
137
|
-
this.idleWaiters.clear();
|
|
138
|
-
for (const resolve of waiters) resolve();
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async awaitIdle() {
|
|
143
|
-
if (this.activeUses === 0) return;
|
|
144
|
-
await new Promise((resolve) => {
|
|
145
|
-
this.idleWaiters.add(resolve);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
async createProvider() {
|
|
149
|
-
return await runProviderAdapterLifecycle(async () => {
|
|
150
|
-
await drainRetainedProviders();
|
|
151
|
-
return await this.createProviderAfterRetirement();
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
async createProviderAfterRetirement() {
|
|
155
|
-
const cfg = this.api.runtime.config?.current?.() ?? this.api.config;
|
|
156
|
-
const providerId = this.embedding.provider;
|
|
157
|
-
const { getMemoryEmbeddingProvider } = await loadMemoryEmbeddingProviderModule();
|
|
158
|
-
const adapter = getMemoryEmbeddingProvider(providerId, cfg);
|
|
159
|
-
if (!adapter) throw new Error(`Unknown memory embedding provider: ${providerId}`);
|
|
160
|
-
const { resolveDefaultAgentId } = await loadMemoryHostCoreModule();
|
|
161
|
-
const defaultAgentId = resolveDefaultAgentId(cfg);
|
|
162
|
-
const agentDir = this.api.runtime.agent.resolveAgentDir(cfg, defaultAgentId);
|
|
163
|
-
const remote = this.embedding.apiKey || this.embedding.baseUrl ? {
|
|
164
|
-
...this.embedding.apiKey ? { apiKey: this.embedding.apiKey } : {},
|
|
165
|
-
...this.embedding.baseUrl ? { baseUrl: this.embedding.baseUrl } : {}
|
|
166
|
-
} : void 0;
|
|
167
|
-
const result = await adapter.create({
|
|
168
|
-
config: cfg,
|
|
169
|
-
agentDir,
|
|
170
|
-
provider: providerId,
|
|
171
|
-
fallback: "none",
|
|
172
|
-
model: this.embedding.model,
|
|
173
|
-
...remote ? { remote } : {},
|
|
174
|
-
...typeof this.embedding.dimensions === "number" ? { outputDimensionality: this.embedding.dimensions } : {}
|
|
175
|
-
});
|
|
176
|
-
if (!result.provider) throw new Error(`Memory embedding provider ${providerId} is unavailable.`);
|
|
177
|
-
return result.provider;
|
|
178
|
-
}
|
|
179
|
-
async embed(text, options) {
|
|
180
|
-
const releaseUse = this.acquireUse();
|
|
181
|
-
try {
|
|
182
|
-
const provider = await this.getProvider();
|
|
183
|
-
if (!options?.timeoutMs) return await provider.embedQuery(text);
|
|
184
|
-
const controller = new AbortController();
|
|
185
|
-
let timer;
|
|
186
|
-
try {
|
|
187
|
-
timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("memory-lancedb embedding timed out")), resolveTimerTimeoutMs(options.timeoutMs, 1));
|
|
188
|
-
timer.unref?.();
|
|
189
|
-
return await provider.embedQuery(text, { signal: controller.signal });
|
|
190
|
-
} finally {
|
|
191
|
-
if (timer) clearTimeout(timer);
|
|
192
|
-
}
|
|
193
|
-
} finally {
|
|
194
|
-
releaseUse();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async close() {
|
|
198
|
-
const existingClose = this.closePromise;
|
|
199
|
-
if (existingClose) {
|
|
200
|
-
await existingClose;
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const closeOperation = this.closeOnce();
|
|
204
|
-
this.closePromise = closeOperation;
|
|
205
|
-
try {
|
|
206
|
-
await closeOperation;
|
|
207
|
-
} catch (err) {
|
|
208
|
-
if (this.closePromise === closeOperation) this.closePromise = null;
|
|
209
|
-
throw err;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
async closeOnce() {
|
|
213
|
-
this.closed = true;
|
|
214
|
-
const providerPromise = this.providerPromise;
|
|
215
|
-
await runProviderAdapterLifecycle(async () => {
|
|
216
|
-
await this.awaitIdle();
|
|
217
|
-
const provider = await providerPromise?.catch(() => null);
|
|
218
|
-
if (provider) PROVIDER_ADAPTER_LIFECYCLE.retainedProviders.add(provider);
|
|
219
|
-
try {
|
|
220
|
-
await drainRetainedProviders();
|
|
221
|
-
} finally {
|
|
222
|
-
if (this.providerPromise === providerPromise) this.providerPromise = void 0;
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
async function runWithTimeout(params) {
|
|
228
|
-
let timeout;
|
|
229
|
-
const TIMEOUT = Symbol("timeout");
|
|
230
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
231
|
-
timeout = setTimeout(() => resolve(TIMEOUT), resolveTimerTimeoutMs(params.timeoutMs, 1));
|
|
232
|
-
timeout.unref?.();
|
|
233
|
-
});
|
|
234
|
-
const taskPromise = params.task();
|
|
235
|
-
taskPromise.catch(() => void 0);
|
|
236
|
-
try {
|
|
237
|
-
const result = await Promise.race([taskPromise, timeoutPromise]);
|
|
238
|
-
if (result === TIMEOUT) return { status: "timeout" };
|
|
239
|
-
return {
|
|
240
|
-
status: "ok",
|
|
241
|
-
value: result
|
|
242
|
-
};
|
|
243
|
-
} finally {
|
|
244
|
-
if (timeout) clearTimeout(timeout);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function formatMemoryRecallError(error) {
|
|
248
|
-
return error instanceof Error ? error.message : String(error);
|
|
249
|
-
}
|
|
250
|
-
function isMemoryRecallTimeoutError(error) {
|
|
251
|
-
let current = error;
|
|
252
|
-
for (let depth = 0; depth < 3 && current !== void 0; depth += 1) {
|
|
253
|
-
const record = asOptionalRecord(current);
|
|
254
|
-
const name = current instanceof Error ? current.name : typeof record?.name === "string" ? record.name : "";
|
|
255
|
-
const message = current instanceof Error ? current.message : typeof record?.message === "string" ? record.message : "";
|
|
256
|
-
const code = typeof record?.code === "string" ? record.code : "";
|
|
257
|
-
if (name === "APIConnectionTimeoutError" || name === "TimeoutError" || code === "ETIMEDOUT" || /^UND_ERR_.*_TIMEOUT$/.test(code) || /\btimed out\b/i.test(message)) return true;
|
|
258
|
-
current = record?.cause;
|
|
259
|
-
}
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
function buildMemoryRecallUnavailableResult(error) {
|
|
263
|
-
return {
|
|
264
|
-
content: [{
|
|
265
|
-
type: "text",
|
|
266
|
-
text: "Memory recall is unavailable right now."
|
|
267
|
-
}],
|
|
268
|
-
details: {
|
|
269
|
-
count: 0,
|
|
270
|
-
disabled: true,
|
|
271
|
-
unavailable: true,
|
|
272
|
-
error
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
var MemoryRecallEmbeddingError = class extends Error {
|
|
277
|
-
constructor(originalError) {
|
|
278
|
-
super(formatMemoryRecallError(originalError));
|
|
279
|
-
this.originalError = originalError;
|
|
280
|
-
this.name = "MemoryRecallEmbeddingError";
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
const testing = {
|
|
284
|
-
isEmbeddingDimensionsRejectedError,
|
|
285
|
-
isMemoryRecallTimeoutError,
|
|
286
|
-
runWithTimeout,
|
|
287
|
-
truncateEmbeddingVector
|
|
288
|
-
};
|
|
289
|
-
function createEmbeddings(api, cfg) {
|
|
290
|
-
const { provider, model, dimensions, apiKey, baseUrl } = cfg.embedding;
|
|
291
|
-
if (provider === "openai" && apiKey) return new OpenAiCompatibleEmbeddings(apiKey, model, baseUrl, dimensions);
|
|
292
|
-
return new ProviderAdapterEmbeddings(api, cfg.embedding);
|
|
293
|
-
}
|
|
294
|
-
function normalizeEmbeddingVector(value) {
|
|
295
|
-
if (Array.isArray(value)) {
|
|
296
|
-
if (!value.every((item) => typeof item === "number" && Number.isFinite(item))) throw new Error("Embedding response contains non-numeric values");
|
|
297
|
-
return value;
|
|
298
|
-
}
|
|
299
|
-
if (typeof value === "string") {
|
|
300
|
-
const canonicalEmbedding = canonicalizeBase64(value);
|
|
301
|
-
if (!canonicalEmbedding) throw new Error("Base64 embedding response is malformed");
|
|
302
|
-
const bytes = Buffer.from(canonicalEmbedding, "base64");
|
|
303
|
-
if (bytes.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0) throw new Error("Base64 embedding response has invalid byte length");
|
|
304
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
305
|
-
const floats = [];
|
|
306
|
-
for (let offset = 0; offset < bytes.byteLength; offset += Float32Array.BYTES_PER_ELEMENT) floats.push(view.getFloat32(offset, true));
|
|
307
|
-
return floats;
|
|
308
|
-
}
|
|
309
|
-
throw new Error("Embedding response is missing a vector");
|
|
310
|
-
}
|
|
311
|
-
//#endregion
|
|
312
|
-
export { MemoryRecallEmbeddingError, buildMemoryRecallUnavailableResult, createEmbeddings, formatMemoryRecallError, isMemoryRecallTimeoutError, normalizeEmbeddingVector, runWithTimeout, testing };
|
package/dist/lancedb-schema.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region extensions/memory-lancedb/lancedb-schema.ts
|
|
2
|
-
const MEMORY_TABLE_NAME = "memories";
|
|
3
|
-
const MEMORY_AGENT_ID_COLUMN = "agentId";
|
|
4
|
-
function quoteLanceSqlString(value) {
|
|
5
|
-
return `'${value.replaceAll("'", "''")}'`;
|
|
6
|
-
}
|
|
7
|
-
function memoryAgentPredicate(agentId) {
|
|
8
|
-
return `${MEMORY_AGENT_ID_COLUMN} = ${quoteLanceSqlString(agentId)}`;
|
|
9
|
-
}
|
|
10
|
-
function hasAgentScopeColumn(schema) {
|
|
11
|
-
return schema.fields.some((field) => field.name === MEMORY_AGENT_ID_COLUMN);
|
|
12
|
-
}
|
|
13
|
-
function legacyMemorySchemaError() {
|
|
14
|
-
return /* @__PURE__ */ new Error("memory-lancedb: the existing memory table predates per-agent isolation. Run \"openclaw doctor --fix\" to assign legacy rows to the default agent, then restart OpenClaw.");
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
export { MEMORY_AGENT_ID_COLUMN, MEMORY_TABLE_NAME, hasAgentScopeColumn, legacyMemorySchemaError, memoryAgentPredicate, quoteLanceSqlString };
|