@xdarkicex/openclaw-memory-libravdb 1.4.5 → 1.4.7
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/HOOK.md +14 -0
- package/README.md +32 -2
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +208 -0
- package/dist/context-engine.d.ts +56 -0
- package/dist/context-engine.js +125 -0
- package/dist/dream-promotion.d.ts +47 -0
- package/dist/dream-promotion.js +363 -0
- package/dist/dream-routing.d.ts +6 -0
- package/dist/dream-routing.js +31 -0
- package/dist/durable-namespace.d.ts +6 -0
- package/dist/durable-namespace.js +24 -0
- package/dist/grpc-client.d.ts +23 -0
- package/dist/grpc-client.js +104 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +40 -0
- package/dist/lifecycle-hooks.d.ts +4 -0
- package/dist/lifecycle-hooks.js +64 -0
- package/dist/markdown-hash.d.ts +3 -0
- package/dist/markdown-hash.js +82 -0
- package/dist/markdown-ingest.d.ts +43 -0
- package/dist/markdown-ingest.js +464 -0
- package/dist/memory-provider.d.ts +4 -0
- package/dist/memory-provider.js +13 -0
- package/dist/memory-runtime.d.ts +118 -0
- package/dist/memory-runtime.js +217 -0
- package/dist/plugin-runtime.d.ts +28 -0
- package/dist/plugin-runtime.js +127 -0
- package/dist/proto/intelligence_kernel/v1/kernel.proto +378 -0
- package/dist/recall-cache.d.ts +2 -0
- package/dist/recall-cache.js +30 -0
- package/dist/rpc-protobuf-codecs.d.ts +70 -0
- package/dist/rpc-protobuf-codecs.js +77 -0
- package/dist/rpc.d.ts +14 -0
- package/dist/rpc.js +121 -0
- package/dist/sidecar.d.ts +34 -0
- package/dist/sidecar.js +535 -0
- package/dist/types.d.ts +163 -0
- package/dist/types.js +1 -0
- package/docs/contributing.md +14 -13
- package/docs/install.md +7 -9
- package/docs/installation.md +23 -16
- package/docs/uninstall.md +1 -1
- package/index.js +2 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +39 -16
- package/packaging/README.md +0 -71
- package/packaging/homebrew/libravdbd.rb.tmpl +0 -224
- package/packaging/launchd/com.xdarkicex.libravdbd.plist +0 -32
- package/packaging/systemd/libravdbd.service +0 -12
- package/src/cli.ts +0 -299
- package/src/comparison-experiments.ts +0 -128
- package/src/context-engine.ts +0 -1451
- package/src/continuity.ts +0 -93
- package/src/dream-promotion.ts +0 -492
- package/src/dream-routing.ts +0 -40
- package/src/durable-namespace.ts +0 -34
- package/src/index.ts +0 -47
- package/src/lifecycle-hooks.ts +0 -96
- package/src/markdown-hash.ts +0 -104
- package/src/markdown-ingest.ts +0 -627
- package/src/memory-provider.ts +0 -25
- package/src/memory-runtime.ts +0 -283
- package/src/openclaw-plugin-sdk.d.ts +0 -59
- package/src/plugin-runtime.ts +0 -116
- package/src/recall-cache.ts +0 -34
- package/src/recall-utils.ts +0 -131
- package/src/rpc.ts +0 -84
- package/src/scoring.ts +0 -632
- package/src/sidecar.ts +0 -486
- package/src/temporal.ts +0 -1010
- package/src/tokens.ts +0 -52
- package/src/types.ts +0 -277
- package/tsconfig.json +0 -20
- package/tsconfig.tests.json +0 -12
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getHashBackendName, hashBytes } from "./markdown-hash.js";
|
|
5
|
+
const DEFAULT_DEBOUNCE_MS = 150;
|
|
6
|
+
const DEFAULT_MIN_SCORE = 0.6;
|
|
7
|
+
const DEFAULT_MIN_RECALL_COUNT = 2;
|
|
8
|
+
const DEFAULT_MIN_UNIQUE_QUERIES = 2;
|
|
9
|
+
const DREAM_PROMOTION_VERSION = 1;
|
|
10
|
+
const DREAM_SOURCE_KIND = "dream";
|
|
11
|
+
export function createDreamPromotionHandle(cfg, getRpc, logger = console, fsApi = createRealFsApi()) {
|
|
12
|
+
const diaryPath = normalizeDiaryPath(cfg.dreamPromotionDiaryPath);
|
|
13
|
+
const userId = cfg.dreamPromotionUserId?.trim() ?? "";
|
|
14
|
+
if (cfg.dreamPromotionEnabled !== true || !diaryPath || !userId) {
|
|
15
|
+
return {
|
|
16
|
+
async start() { },
|
|
17
|
+
async refresh() { },
|
|
18
|
+
async stop() { },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const state = {
|
|
22
|
+
watching: false,
|
|
23
|
+
dirty: false,
|
|
24
|
+
timer: null,
|
|
25
|
+
watcher: null,
|
|
26
|
+
};
|
|
27
|
+
let lastFileState = null;
|
|
28
|
+
const debounceMs = cfg.dreamPromotionDebounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
29
|
+
return {
|
|
30
|
+
async start() {
|
|
31
|
+
if (state.watching) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
state.watching = true;
|
|
35
|
+
await refreshDiary();
|
|
36
|
+
},
|
|
37
|
+
async refresh() {
|
|
38
|
+
await refreshDiary();
|
|
39
|
+
},
|
|
40
|
+
async stop() {
|
|
41
|
+
state.watching = false;
|
|
42
|
+
if (state.timer) {
|
|
43
|
+
clearTimeout(state.timer);
|
|
44
|
+
state.timer = null;
|
|
45
|
+
}
|
|
46
|
+
if (state.watcher) {
|
|
47
|
+
state.watcher.close();
|
|
48
|
+
state.watcher = null;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
async function refreshDiary() {
|
|
53
|
+
if (!state.watching) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (state.timer) {
|
|
57
|
+
state.dirty = true;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
state.timer = setTimeout(() => {
|
|
61
|
+
state.timer = null;
|
|
62
|
+
void scanDiary().catch((error) => {
|
|
63
|
+
logger.warn?.(`[dream-promotion] refresh failed for ${diaryPath}: ${formatError(error)}`);
|
|
64
|
+
});
|
|
65
|
+
}, debounceMs);
|
|
66
|
+
}
|
|
67
|
+
async function scanDiary() {
|
|
68
|
+
if (!state.watching) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await ensureWatcher();
|
|
72
|
+
const stat = await safeStat(diaryPath);
|
|
73
|
+
if (!stat) {
|
|
74
|
+
lastFileState = null;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (lastFileState && lastFileState.size === stat.size && lastFileState.mtimeMs === stat.mtimeMs) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const bytes = await safeReadFile(diaryPath);
|
|
81
|
+
if (!bytes) {
|
|
82
|
+
lastFileState = null;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const fileHash = hashBytes(bytes);
|
|
86
|
+
if (lastFileState && lastFileState.fileHash === fileHash) {
|
|
87
|
+
lastFileState = {
|
|
88
|
+
size: stat.size,
|
|
89
|
+
mtimeMs: stat.mtimeMs,
|
|
90
|
+
fileHash,
|
|
91
|
+
};
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const text = textDecoder.decode(bytes);
|
|
95
|
+
const candidates = parseDreamPromotionCandidates(text);
|
|
96
|
+
if (candidates.length === 0) {
|
|
97
|
+
lastFileState = {
|
|
98
|
+
size: stat.size,
|
|
99
|
+
mtimeMs: stat.mtimeMs,
|
|
100
|
+
fileHash,
|
|
101
|
+
};
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const rpc = await getRpc();
|
|
105
|
+
const params = {
|
|
106
|
+
userId,
|
|
107
|
+
sourceDoc: diaryPath,
|
|
108
|
+
sourceRoot: path.dirname(diaryPath),
|
|
109
|
+
sourcePath: path.basename(diaryPath),
|
|
110
|
+
sourceKind: DREAM_SOURCE_KIND,
|
|
111
|
+
fileHash,
|
|
112
|
+
sourceSize: stat.size,
|
|
113
|
+
sourceMtimeMs: stat.mtimeMs,
|
|
114
|
+
ingestVersion: DREAM_PROMOTION_VERSION,
|
|
115
|
+
hashBackend: getHashBackendName(),
|
|
116
|
+
entries: candidates.map((candidate, index) => ({
|
|
117
|
+
...candidate,
|
|
118
|
+
sourceLine: candidate.line,
|
|
119
|
+
line: index + 1,
|
|
120
|
+
})),
|
|
121
|
+
};
|
|
122
|
+
await rpc.call("promote_dream_entries", params);
|
|
123
|
+
lastFileState = {
|
|
124
|
+
size: stat.size,
|
|
125
|
+
mtimeMs: stat.mtimeMs,
|
|
126
|
+
fileHash,
|
|
127
|
+
};
|
|
128
|
+
if (state.dirty) {
|
|
129
|
+
state.dirty = false;
|
|
130
|
+
await refreshDiary();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function safeStat(filePath) {
|
|
134
|
+
try {
|
|
135
|
+
return await fsApi.stat(filePath);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function safeReadFile(filePath) {
|
|
142
|
+
try {
|
|
143
|
+
return await fsApi.readFile(filePath);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function ensureWatcher() {
|
|
150
|
+
if (state.watcher) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const parentDir = path.dirname(diaryPath);
|
|
154
|
+
try {
|
|
155
|
+
const watcher = fsApi.watch(parentDir, (_event, filename) => {
|
|
156
|
+
if (filename && path.basename(String(filename)) !== path.basename(diaryPath)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
state.dirty = true;
|
|
160
|
+
void refreshDiary();
|
|
161
|
+
});
|
|
162
|
+
watcher.on("error", (error) => {
|
|
163
|
+
logger.warn?.(`[dream-promotion] watch error for ${parentDir}: ${formatError(error)}`);
|
|
164
|
+
});
|
|
165
|
+
state.watcher = watcher;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
logger.warn?.(`[dream-promotion] watch unavailable for ${parentDir}: ${formatError(error)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export async function promoteDreamDiaryFile(rpc, opts) {
|
|
173
|
+
const diaryPath = normalizeDiaryPath(opts.diaryPath);
|
|
174
|
+
if (!diaryPath) {
|
|
175
|
+
throw new Error("dream diary path is required");
|
|
176
|
+
}
|
|
177
|
+
const userId = opts.userId.trim();
|
|
178
|
+
if (!userId) {
|
|
179
|
+
throw new Error("user id is required");
|
|
180
|
+
}
|
|
181
|
+
let text = opts.text;
|
|
182
|
+
let fileHash = opts.fileHash;
|
|
183
|
+
let sourceSize = opts.sourceSize;
|
|
184
|
+
let sourceMtimeMs = opts.sourceMtimeMs;
|
|
185
|
+
if (text == null) {
|
|
186
|
+
const bytes = await fsp.readFile(diaryPath);
|
|
187
|
+
text = textDecoder.decode(bytes);
|
|
188
|
+
fileHash = fileHash ?? hashBytes(bytes);
|
|
189
|
+
const stat = await fsp.stat(diaryPath);
|
|
190
|
+
sourceSize = sourceSize ?? stat.size;
|
|
191
|
+
sourceMtimeMs = sourceMtimeMs ?? stat.mtimeMs;
|
|
192
|
+
}
|
|
193
|
+
const candidates = parseDreamPromotionCandidates(text);
|
|
194
|
+
return await rpc.call("promote_dream_entries", {
|
|
195
|
+
userId,
|
|
196
|
+
sourceDoc: diaryPath,
|
|
197
|
+
sourceRoot: path.dirname(diaryPath),
|
|
198
|
+
sourcePath: path.basename(diaryPath),
|
|
199
|
+
sourceKind: DREAM_SOURCE_KIND,
|
|
200
|
+
fileHash: fileHash ?? "",
|
|
201
|
+
sourceSize: sourceSize ?? 0,
|
|
202
|
+
sourceMtimeMs: sourceMtimeMs ?? 0,
|
|
203
|
+
ingestVersion: DREAM_PROMOTION_VERSION,
|
|
204
|
+
hashBackend: getHashBackendName(),
|
|
205
|
+
entries: candidates.map((candidate, index) => ({
|
|
206
|
+
...candidate,
|
|
207
|
+
sourceLine: candidate.line,
|
|
208
|
+
line: index + 1,
|
|
209
|
+
})),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export function parseDreamPromotionCandidates(text) {
|
|
213
|
+
const candidates = [];
|
|
214
|
+
const lines = text.split("\n");
|
|
215
|
+
let inFence = false;
|
|
216
|
+
let activeSection = "";
|
|
217
|
+
for (let index = 0; index < lines.length; index++) {
|
|
218
|
+
const line = lines[index] ?? "";
|
|
219
|
+
const trimmed = line.trimStart();
|
|
220
|
+
if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
|
|
221
|
+
inFence = !inFence;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (inFence) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const heading = parseHeading(trimmed);
|
|
228
|
+
if (heading) {
|
|
229
|
+
activeSection = heading;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!isPromotionSection(activeSection)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const bullet = parseBulletCandidate(line);
|
|
236
|
+
if (!bullet) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const metadata = parseTrailingMetadata(bullet.body);
|
|
240
|
+
if (!metadata) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const textValue = bullet.body.slice(0, metadata.bodyStart).trim();
|
|
244
|
+
if (!textValue) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
candidates.push({
|
|
248
|
+
text: textValue,
|
|
249
|
+
score: metadata.score,
|
|
250
|
+
recallCount: metadata.recallCount,
|
|
251
|
+
uniqueQueries: metadata.uniqueQueries,
|
|
252
|
+
section: activeSection,
|
|
253
|
+
line: index + 1,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return candidates;
|
|
257
|
+
}
|
|
258
|
+
function parseHeading(value) {
|
|
259
|
+
const match = /^(#{2,6})\s+(.+)$/.exec(value);
|
|
260
|
+
if (!match) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return normalizeSectionName(match[2] ?? "");
|
|
264
|
+
}
|
|
265
|
+
function isPromotionSection(section) {
|
|
266
|
+
return section.includes("deep sleep") || section.includes("promot") || section.includes("dream");
|
|
267
|
+
}
|
|
268
|
+
function parseBulletCandidate(line) {
|
|
269
|
+
const match = /^\s*[-*+]\s+(.+)$/.exec(line);
|
|
270
|
+
if (!match) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return { body: match[1] ?? "" };
|
|
274
|
+
}
|
|
275
|
+
function parseTrailingMetadata(body) {
|
|
276
|
+
const trimmed = body.trimEnd();
|
|
277
|
+
if (!trimmed.endsWith("}")) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const open = trimmed.lastIndexOf("{");
|
|
281
|
+
if (open < 0) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const metadataText = trimmed.slice(open + 1, -1).trim();
|
|
285
|
+
const text = trimmed.slice(0, open).trimEnd();
|
|
286
|
+
if (!metadataText || !text) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const fields = new Map();
|
|
290
|
+
for (const token of metadataText.split(/[,\s]+/)) {
|
|
291
|
+
if (!token) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const equals = token.indexOf("=");
|
|
295
|
+
if (equals <= 0) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const key = token.slice(0, equals).trim().toLowerCase();
|
|
299
|
+
const value = token.slice(equals + 1).trim();
|
|
300
|
+
if (key && value) {
|
|
301
|
+
fields.set(key, value);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const score = parseNumber(fields.get("score"));
|
|
305
|
+
const recallCount = parseInteger(fields.get("recall") ?? fields.get("recallcount"));
|
|
306
|
+
const uniqueQueries = parseInteger(fields.get("unique") ?? fields.get("uniquequeries"));
|
|
307
|
+
if (score == null || recallCount == null || uniqueQueries == null) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
bodyStart: text.length,
|
|
312
|
+
score,
|
|
313
|
+
recallCount,
|
|
314
|
+
uniqueQueries,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function parseNumber(value) {
|
|
318
|
+
if (!value) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const parsed = Number.parseFloat(value);
|
|
322
|
+
if (!Number.isFinite(parsed)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return parsed;
|
|
326
|
+
}
|
|
327
|
+
function parseInteger(value) {
|
|
328
|
+
if (!value) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const parsed = Number.parseInt(value, 10);
|
|
332
|
+
if (!Number.isFinite(parsed)) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return parsed;
|
|
336
|
+
}
|
|
337
|
+
function normalizeSectionName(value) {
|
|
338
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
339
|
+
}
|
|
340
|
+
function normalizeDiaryPath(value) {
|
|
341
|
+
const trimmed = value?.trim();
|
|
342
|
+
if (!trimmed) {
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
345
|
+
return path.resolve(trimmed);
|
|
346
|
+
}
|
|
347
|
+
function createRealFsApi() {
|
|
348
|
+
return {
|
|
349
|
+
readFile: async (file) => fsp.readFile(file),
|
|
350
|
+
stat: async (file) => {
|
|
351
|
+
const stat = await fsp.stat(file);
|
|
352
|
+
return { size: stat.size, mtimeMs: stat.mtimeMs };
|
|
353
|
+
},
|
|
354
|
+
watch: (dir, onChange) => fs.watch(dir, onChange),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function formatError(error) {
|
|
358
|
+
if (error instanceof Error) {
|
|
359
|
+
return error.message;
|
|
360
|
+
}
|
|
361
|
+
return String(error);
|
|
362
|
+
}
|
|
363
|
+
const textDecoder = new TextDecoder();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const DREAM_COLLECTION_PREFIX = "dream:";
|
|
2
|
+
const DREAM_PATTERN_RULES = [
|
|
3
|
+
{
|
|
4
|
+
label: "dream",
|
|
5
|
+
patterns: [
|
|
6
|
+
/\bdream(?:s|ed|ing)?\b/i,
|
|
7
|
+
/\btell\s+me\s+about\s+(?:your\s+)?dreams?\b/i,
|
|
8
|
+
/\bwhat\s+did\s+i\s+dream\s+about\b/i,
|
|
9
|
+
/\bwhat\s+was\s+i\s+dreaming\s+about\b/i,
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
const DREAM_MATCHED_PATTERNS = ["dream"];
|
|
14
|
+
const EMPTY_MATCHED_PATTERNS = [];
|
|
15
|
+
export function detectDreamQuerySignal(queryText) {
|
|
16
|
+
for (const rule of DREAM_PATTERN_RULES) {
|
|
17
|
+
if (rule.patterns.some((pattern) => pattern.test(queryText))) {
|
|
18
|
+
return {
|
|
19
|
+
active: true,
|
|
20
|
+
matchedPatterns: DREAM_MATCHED_PATTERNS,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
active: false,
|
|
26
|
+
matchedPatterns: EMPTY_MATCHED_PATTERNS,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function resolveDreamCollection(userId) {
|
|
30
|
+
return `${DREAM_COLLECTION_PREFIX}${userId.trim()}`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const SESSION_KEY_NAMESPACE_PREFIX = "session-key:";
|
|
2
|
+
const AGENT_ID_NAMESPACE_PREFIX = "agent-id:";
|
|
3
|
+
export function resolveDurableNamespace(params) {
|
|
4
|
+
const explicitUserId = firstNonEmpty(params.userId);
|
|
5
|
+
if (explicitUserId) {
|
|
6
|
+
return explicitUserId;
|
|
7
|
+
}
|
|
8
|
+
const sessionKey = firstNonEmpty(params.sessionKey);
|
|
9
|
+
if (sessionKey) {
|
|
10
|
+
return `${SESSION_KEY_NAMESPACE_PREFIX}${sessionKey}`;
|
|
11
|
+
}
|
|
12
|
+
const agentId = firstNonEmpty(params.agentId);
|
|
13
|
+
if (agentId) {
|
|
14
|
+
return `${AGENT_ID_NAMESPACE_PREFIX}${agentId}`;
|
|
15
|
+
}
|
|
16
|
+
return firstNonEmpty(params.fallback) ?? "default";
|
|
17
|
+
}
|
|
18
|
+
function firstNonEmpty(value) {
|
|
19
|
+
if (typeof value !== "string") {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface GrpcClientOptions {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
secret?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class GrpcKernelClient {
|
|
7
|
+
private client;
|
|
8
|
+
private readonly secret;
|
|
9
|
+
private readonly timeoutMs;
|
|
10
|
+
private nonceHex;
|
|
11
|
+
constructor(options: GrpcClientOptions);
|
|
12
|
+
private getMetadata;
|
|
13
|
+
private call;
|
|
14
|
+
initializeSession(req: any): Promise<any>;
|
|
15
|
+
assembleContext(req: any): Promise<any>;
|
|
16
|
+
rankCandidates(req: any): Promise<any>;
|
|
17
|
+
ingestMessage(req: any): Promise<any>;
|
|
18
|
+
afterTurn(req: any): Promise<any>;
|
|
19
|
+
bootstrapSession(req: any): Promise<any>;
|
|
20
|
+
compactSession(req: any): Promise<any>;
|
|
21
|
+
getStatus(req?: any): Promise<any>;
|
|
22
|
+
close(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as grpc from "@grpc/grpc-js";
|
|
5
|
+
import * as protoLoader from "@grpc/proto-loader";
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
// The proto file is expected to be copied to dist/proto/ at build time.
|
|
8
|
+
// In source, it's at api/proto/.
|
|
9
|
+
const PROTO_PATH = path.resolve(__dirname, "./proto/intelligence_kernel/v1/kernel.proto");
|
|
10
|
+
export class GrpcKernelClient {
|
|
11
|
+
client;
|
|
12
|
+
secret;
|
|
13
|
+
timeoutMs;
|
|
14
|
+
nonceHex;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.secret = options.secret;
|
|
17
|
+
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
18
|
+
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
19
|
+
keepCase: true,
|
|
20
|
+
longs: String,
|
|
21
|
+
enums: String,
|
|
22
|
+
defaults: true,
|
|
23
|
+
oneofs: true,
|
|
24
|
+
});
|
|
25
|
+
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
|
26
|
+
const kernelService = protoDescriptor.intelligence_kernel.v1.IntelligenceKernel;
|
|
27
|
+
const target = options.endpoint.startsWith("tcp:")
|
|
28
|
+
? options.endpoint.substring(4)
|
|
29
|
+
: options.endpoint.startsWith("unix:")
|
|
30
|
+
? options.endpoint.substring(5)
|
|
31
|
+
: options.endpoint;
|
|
32
|
+
this.client = new kernelService(target, grpc.credentials.createInsecure());
|
|
33
|
+
}
|
|
34
|
+
getMetadata(signed = true) {
|
|
35
|
+
const md = new grpc.Metadata();
|
|
36
|
+
if (this.secret && signed) {
|
|
37
|
+
if (!this.nonceHex) {
|
|
38
|
+
throw new Error("call initializeSession before authenticated RPCs");
|
|
39
|
+
}
|
|
40
|
+
const hmac = createHmac("sha256", Buffer.from(this.nonceHex, "hex"));
|
|
41
|
+
hmac.update(this.secret);
|
|
42
|
+
const signature = hmac.digest("hex");
|
|
43
|
+
md.add("x-libravdb-auth", signature);
|
|
44
|
+
}
|
|
45
|
+
return md;
|
|
46
|
+
}
|
|
47
|
+
call(method, req, signed = true) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const deadline = new Date(Date.now() + this.timeoutMs);
|
|
50
|
+
this.client[method](req, this.getMetadata(signed), { deadline }, (err, resp) => {
|
|
51
|
+
if (err) {
|
|
52
|
+
reject(err);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
resolve(resp);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async initializeSession(req) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const deadline = new Date(Date.now() + this.timeoutMs);
|
|
63
|
+
this.client.InitializeSession(req, this.getMetadata(false), { deadline }, (err, resp) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
reject(err);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const nonce = resp?.server_metadata?.nonce;
|
|
69
|
+
if (this.secret && (typeof nonce !== "string" || nonce.length === 0)) {
|
|
70
|
+
reject(new Error("InitializeSession response missing auth nonce"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (typeof nonce === "string" && nonce.length > 0) {
|
|
74
|
+
this.nonceHex = nonce;
|
|
75
|
+
}
|
|
76
|
+
resolve(resp);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async assembleContext(req) {
|
|
81
|
+
return this.call("AssembleContext", req);
|
|
82
|
+
}
|
|
83
|
+
async rankCandidates(req) {
|
|
84
|
+
return this.call("RankCandidates", req);
|
|
85
|
+
}
|
|
86
|
+
async ingestMessage(req) {
|
|
87
|
+
return this.call("IngestMessage", req);
|
|
88
|
+
}
|
|
89
|
+
async afterTurn(req) {
|
|
90
|
+
return this.call("AfterTurn", req);
|
|
91
|
+
}
|
|
92
|
+
async bootstrapSession(req) {
|
|
93
|
+
return this.call("BootstrapSession", req);
|
|
94
|
+
}
|
|
95
|
+
async compactSession(req) {
|
|
96
|
+
return this.call("CompactSession", req);
|
|
97
|
+
}
|
|
98
|
+
async getStatus(req = {}) {
|
|
99
|
+
return this.call("GetStatus", req);
|
|
100
|
+
}
|
|
101
|
+
close() {
|
|
102
|
+
this.client.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
kind?: "memory" | "context-engine" | Array<"memory" | "context-engine">;
|
|
7
|
+
configSchema?: unknown;
|
|
8
|
+
register(api: OpenClawPluginApi): void | Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { registerMemoryCli } from "./cli.js";
|
|
3
|
+
import { buildContextEngineFactory } from "./context-engine.js";
|
|
4
|
+
import { createBeforeResetHook, createSessionEndHook } from "./lifecycle-hooks.js";
|
|
5
|
+
import { createDreamPromotionHandle } from "./dream-promotion.js";
|
|
6
|
+
import { createMarkdownIngestionHandle } from "./markdown-ingest.js";
|
|
7
|
+
import { buildMemoryPromptSection } from "./memory-provider.js";
|
|
8
|
+
import { buildMemoryRuntimeBridge } from "./memory-runtime.js";
|
|
9
|
+
import { createRecallCache } from "./recall-cache.js";
|
|
10
|
+
import { createPluginRuntime } from "./plugin-runtime.js";
|
|
11
|
+
export default definePluginEntry({
|
|
12
|
+
id: "libravdb-memory",
|
|
13
|
+
name: "LibraVDB Memory",
|
|
14
|
+
description: "Persistent vector memory with three-tier hybrid scoring",
|
|
15
|
+
kind: "context-engine",
|
|
16
|
+
register(api) {
|
|
17
|
+
const cfg = api.pluginConfig;
|
|
18
|
+
const recallCache = createRecallCache();
|
|
19
|
+
const runtime = createPluginRuntime(cfg, api.logger ?? console);
|
|
20
|
+
const markdownIngestion = createMarkdownIngestionHandle(cfg, runtime.getRpc, api.logger ?? console);
|
|
21
|
+
const dreamPromotion = createDreamPromotionHandle(cfg, runtime.getRpc, api.logger ?? console);
|
|
22
|
+
void markdownIngestion.start().catch((error) => {
|
|
23
|
+
api.logger?.warn?.(`LibraVDB markdown ingestion failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
+
});
|
|
25
|
+
void dreamPromotion.start().catch((error) => {
|
|
26
|
+
api.logger?.warn?.(`LibraVDB dream promotion failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
});
|
|
28
|
+
registerMemoryCli(api, runtime, cfg, api.logger ?? console);
|
|
29
|
+
api.registerContextEngine("libravdb-memory", () => buildContextEngineFactory(runtime, cfg, recallCache, api.logger ?? console));
|
|
30
|
+
api.registerMemoryPromptSection(buildMemoryPromptSection(runtime.getRpc, cfg, recallCache));
|
|
31
|
+
api.registerMemoryRuntime?.(buildMemoryRuntimeBridge(runtime.getRpc, cfg));
|
|
32
|
+
api.on("before_reset", createBeforeResetHook(runtime, api.logger ?? console));
|
|
33
|
+
api.on("session_end", createSessionEndHook(runtime, api.logger ?? console));
|
|
34
|
+
api.on("gateway_stop", async () => {
|
|
35
|
+
await dreamPromotion.stop();
|
|
36
|
+
await markdownIngestion.stop();
|
|
37
|
+
await runtime.shutdown();
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PluginRuntime } from "./plugin-runtime.js";
|
|
2
|
+
import type { LoggerLike } from "./types.js";
|
|
3
|
+
export declare function createBeforeResetHook(runtime: PluginRuntime, logger?: LoggerLike): (event: unknown, ctx: unknown) => Promise<void>;
|
|
4
|
+
export declare function createSessionEndHook(runtime: PluginRuntime, logger?: LoggerLike): (event: unknown, ctx: unknown) => Promise<void>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function createBeforeResetHook(runtime, logger = console) {
|
|
2
|
+
return async (event, ctx) => {
|
|
3
|
+
const typedEvent = asBeforeResetEvent(event);
|
|
4
|
+
const typedCtx = asAgentContext(ctx);
|
|
5
|
+
try {
|
|
6
|
+
await runtime.emitLifecycleHint({
|
|
7
|
+
hook: "before_reset",
|
|
8
|
+
reason: typedEvent.reason,
|
|
9
|
+
sessionFile: typedEvent.sessionFile,
|
|
10
|
+
sessionId: typedCtx.sessionId,
|
|
11
|
+
sessionKey: typedCtx.sessionKey,
|
|
12
|
+
agentId: typedCtx.agentId,
|
|
13
|
+
workspaceDir: typedCtx.workspaceDir,
|
|
14
|
+
messageCount: Array.isArray(typedEvent.messages) ? typedEvent.messages.length : undefined,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
logger.warn?.(`LibraVDB before_reset hint failed: ${formatError(error)}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createSessionEndHook(runtime, logger = console) {
|
|
23
|
+
return async (event, ctx) => {
|
|
24
|
+
const typedEvent = asSessionEndEvent(event);
|
|
25
|
+
const typedCtx = asAgentContext(ctx);
|
|
26
|
+
try {
|
|
27
|
+
await runtime.emitLifecycleHint({
|
|
28
|
+
hook: "session_end",
|
|
29
|
+
reason: typedEvent.reason,
|
|
30
|
+
sessionFile: typedEvent.sessionFile,
|
|
31
|
+
sessionId: typedEvent.sessionId ?? typedCtx.sessionId,
|
|
32
|
+
sessionKey: typedEvent.sessionKey ?? typedCtx.sessionKey,
|
|
33
|
+
agentId: typedCtx.agentId,
|
|
34
|
+
workspaceDir: typedCtx.workspaceDir,
|
|
35
|
+
messageCount: typedEvent.messageCount,
|
|
36
|
+
durationMs: typedEvent.durationMs,
|
|
37
|
+
transcriptArchived: typedEvent.transcriptArchived,
|
|
38
|
+
nextSessionId: typedEvent.nextSessionId,
|
|
39
|
+
nextSessionKey: typedEvent.nextSessionKey,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.warn?.(`LibraVDB session_end hint failed: ${formatError(error)}`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function asAgentContext(value) {
|
|
48
|
+
return isRecord(value) ? value : {};
|
|
49
|
+
}
|
|
50
|
+
function asBeforeResetEvent(value) {
|
|
51
|
+
return isRecord(value) ? value : {};
|
|
52
|
+
}
|
|
53
|
+
function asSessionEndEvent(value) {
|
|
54
|
+
return isRecord(value) ? value : {};
|
|
55
|
+
}
|
|
56
|
+
function isRecord(value) {
|
|
57
|
+
return typeof value === "object" && value !== null;
|
|
58
|
+
}
|
|
59
|
+
function formatError(error) {
|
|
60
|
+
if (error instanceof Error && error.message.trim()) {
|
|
61
|
+
return error.message;
|
|
62
|
+
}
|
|
63
|
+
return String(error);
|
|
64
|
+
}
|