@icex-labs/openclaw-memory-engine 3.4.0 → 3.5.1
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/index.js +36 -1
- package/lib/embedding.js +64 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
19
20
|
|
|
20
21
|
import { resolveWorkspace, getCoreSizeLimit, DEFAULT_TOP_K, MAX_TOP_K } from "./lib/paths.js";
|
|
21
22
|
import { readCore, writeCore, dotGet, dotSet, autoParse } from "./lib/core.js";
|
|
22
23
|
import { loadArchival, appendRecord, rewriteArchival, archivalPath } from "./lib/archival.js";
|
|
23
|
-
import { indexEmbedding, loadEmbeddingCache, saveEmbeddingCache } from "./lib/embedding.js";
|
|
24
|
+
import { indexEmbedding, loadEmbeddingCache, saveEmbeddingCache, backfillEmbeddings } from "./lib/embedding.js";
|
|
24
25
|
import { hybridSearch } from "./lib/search.js";
|
|
25
26
|
import { consolidateText } from "./lib/consolidate.js";
|
|
26
27
|
import { findDuplicates, applyDedup } from "./lib/dedup.js";
|
|
@@ -95,6 +96,40 @@ export default definePluginEntry({
|
|
|
95
96
|
// Factory ctx has: { sessionKey, workspaceDir, agentId, ... }
|
|
96
97
|
// ═══════════════════════════════════════════════════════════════════
|
|
97
98
|
|
|
99
|
+
// Background: auto-backfill missing embeddings on startup (all workspaces)
|
|
100
|
+
setTimeout(async () => {
|
|
101
|
+
try {
|
|
102
|
+
// Discover all unique workspaces from openclaw.json agent configs
|
|
103
|
+
const workspaces = new Set();
|
|
104
|
+
workspaces.add(resolveWorkspace(null)); // default
|
|
105
|
+
try {
|
|
106
|
+
const cfgPath = join(process.env.HOME || "/tmp", ".openclaw", "openclaw.json");
|
|
107
|
+
const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
108
|
+
for (const agent of cfg?.agents?.list || []) {
|
|
109
|
+
if (agent.workspace) workspaces.add(agent.workspace);
|
|
110
|
+
}
|
|
111
|
+
} catch { /* ignore */ }
|
|
112
|
+
|
|
113
|
+
for (const wsDir of workspaces) {
|
|
114
|
+
try {
|
|
115
|
+
const records = loadArchival(wsDir);
|
|
116
|
+
if (records.length === 0) continue;
|
|
117
|
+
const cache = loadEmbeddingCache(wsDir);
|
|
118
|
+
const missing = records.filter((r) => r.id && !cache[r.id]).length;
|
|
119
|
+
if (missing > 0) {
|
|
120
|
+
console.error(`[memory-engine] Backfilling ${missing} embeddings in ${wsDir}...`);
|
|
121
|
+
const result = await backfillEmbeddings(wsDir, records, {
|
|
122
|
+
onProgress: (done, total) => {
|
|
123
|
+
if (done % 500 === 0) console.error(`[memory-engine] ${wsDir}: ${done}/${total}`);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
console.error(`[memory-engine] ${wsDir}: done — ${result.processed} embedded, ${result.errors} errors`);
|
|
127
|
+
}
|
|
128
|
+
} catch { /* skip workspace errors */ }
|
|
129
|
+
}
|
|
130
|
+
} catch { /* ignore startup errors */ }
|
|
131
|
+
}, 10000); // delay 10s after gateway start
|
|
132
|
+
|
|
98
133
|
// ─── core_memory_read ───
|
|
99
134
|
api.registerTool(withAgent((agentId) => ({
|
|
100
135
|
name: "core_memory_read",
|
package/lib/embedding.js
CHANGED
|
@@ -68,3 +68,67 @@ export async function indexEmbedding(ws, record) {
|
|
|
68
68
|
saveEmbeddingCache(ws);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Batch-embed records that are missing from cache.
|
|
74
|
+
* Runs in background with batching (100 per API call) and rate limiting.
|
|
75
|
+
* @param {string} ws - workspace path
|
|
76
|
+
* @param {object[]} records - all archival records
|
|
77
|
+
* @param {object} [options]
|
|
78
|
+
* @param {number} [options.batchSize=100]
|
|
79
|
+
* @param {number} [options.delayMs=200]
|
|
80
|
+
* @param {function} [options.onProgress] - callback(done, total)
|
|
81
|
+
*/
|
|
82
|
+
export async function backfillEmbeddings(ws, records, options = {}) {
|
|
83
|
+
const apiKey = resolveApiKey();
|
|
84
|
+
if (!apiKey) return { processed: 0, errors: 0, skipped: 0 };
|
|
85
|
+
|
|
86
|
+
const batchSize = options.batchSize || 100;
|
|
87
|
+
const delayMs = options.delayMs || 200;
|
|
88
|
+
const cache = loadEmbeddingCache(ws);
|
|
89
|
+
|
|
90
|
+
const missing = records.filter((r) => r.id && !cache[r.id]);
|
|
91
|
+
if (missing.length === 0) return { processed: 0, errors: 0, skipped: 0 };
|
|
92
|
+
|
|
93
|
+
let processed = 0;
|
|
94
|
+
let errors = 0;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < missing.length; i += batchSize) {
|
|
97
|
+
const batch = missing.slice(i, i + batchSize);
|
|
98
|
+
const texts = batch.map((r) =>
|
|
99
|
+
[r.content, r.entity, ...(r.tags || [])].filter(Boolean).join(" "),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch("https://api.openai.com/v1/embeddings", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify({ input: texts, model: EMBEDDING_MODEL, dimensions: EMBEDDING_DIM }),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!res.ok) { errors++; continue; }
|
|
110
|
+
|
|
111
|
+
const data = await res.json();
|
|
112
|
+
for (let j = 0; j < batch.length; j++) {
|
|
113
|
+
if (data.data?.[j]?.embedding) {
|
|
114
|
+
cache[batch[j].id] = data.data[j].embedding;
|
|
115
|
+
processed++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Save after each batch
|
|
120
|
+
saveEmbeddingCache(ws);
|
|
121
|
+
|
|
122
|
+
if (options.onProgress) options.onProgress(processed, missing.length);
|
|
123
|
+
|
|
124
|
+
// Rate limit
|
|
125
|
+
if (i + batchSize < missing.length) {
|
|
126
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
errors++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { processed, errors, skipped: missing.length - processed - errors };
|
|
134
|
+
}
|
package/package.json
CHANGED