@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
package/src/continuity.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_CONTINUITY_MIN_TURNS = 4;
|
|
2
|
-
export const DEFAULT_CONTINUITY_TAIL_BUDGET_TOKENS = 128;
|
|
3
|
-
export const DEFAULT_CONTINUITY_PRIOR_CONTEXT_TOKENS = 96;
|
|
4
|
-
|
|
5
|
-
export interface RecentTailSelection<T> {
|
|
6
|
-
older: T[];
|
|
7
|
-
base: T[];
|
|
8
|
-
recent: T[];
|
|
9
|
-
baseTokens: number;
|
|
10
|
-
recentTokens: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function selectRecentTail<T>(
|
|
14
|
-
items: T[],
|
|
15
|
-
{
|
|
16
|
-
minTurns = DEFAULT_CONTINUITY_MIN_TURNS,
|
|
17
|
-
tailBudgetTokens = DEFAULT_CONTINUITY_TAIL_BUDGET_TOKENS,
|
|
18
|
-
tokenCost,
|
|
19
|
-
sameBundle,
|
|
20
|
-
}: {
|
|
21
|
-
minTurns?: number;
|
|
22
|
-
tailBudgetTokens?: number;
|
|
23
|
-
tokenCost: (item: T) => number;
|
|
24
|
-
sameBundle?: (left: T, right: T) => boolean;
|
|
25
|
-
},
|
|
26
|
-
): RecentTailSelection<T> {
|
|
27
|
-
if (items.length === 0 || minTurns <= 0) {
|
|
28
|
-
return {
|
|
29
|
-
older: [...items],
|
|
30
|
-
base: [],
|
|
31
|
-
recent: [],
|
|
32
|
-
baseTokens: 0,
|
|
33
|
-
recentTokens: 0,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const normalizedMinTurns = Math.max(1, Math.floor(minTurns));
|
|
38
|
-
const normalizedTailBudget = Math.max(0, Math.floor(tailBudgetTokens));
|
|
39
|
-
const baseStart = Math.max(0, items.length - normalizedMinTurns);
|
|
40
|
-
const base = items.slice(baseStart);
|
|
41
|
-
const baseTokens = tokenCostSum(base, tokenCost);
|
|
42
|
-
|
|
43
|
-
if (baseTokens > normalizedTailBudget) {
|
|
44
|
-
const recentStart = extendBundleBoundary(items, baseStart, sameBundle);
|
|
45
|
-
const recent = items.slice(recentStart);
|
|
46
|
-
return {
|
|
47
|
-
older: items.slice(0, recentStart),
|
|
48
|
-
base,
|
|
49
|
-
recent,
|
|
50
|
-
baseTokens,
|
|
51
|
-
recentTokens: tokenCostSum(recent, tokenCost),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let start = baseStart;
|
|
56
|
-
let used = baseTokens;
|
|
57
|
-
for (let i = baseStart - 1; i >= 0; i -= 1) {
|
|
58
|
-
const nextCost = tokenCost(items[i]!);
|
|
59
|
-
if (used + nextCost > normalizedTailBudget) {
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
used += nextCost;
|
|
63
|
-
start = i;
|
|
64
|
-
}
|
|
65
|
-
start = extendBundleBoundary(items, start, sameBundle);
|
|
66
|
-
const recent = items.slice(start);
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
older: items.slice(0, start),
|
|
70
|
-
base,
|
|
71
|
-
recent,
|
|
72
|
-
baseTokens,
|
|
73
|
-
recentTokens: tokenCostSum(recent, tokenCost),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function tokenCostSum<T>(items: T[], tokenCost: (item: T) => number): number {
|
|
78
|
-
return items.reduce((sum, item) => sum + tokenCost(item), 0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function extendBundleBoundary<T>(
|
|
82
|
-
items: T[],
|
|
83
|
-
start: number,
|
|
84
|
-
sameBundle?: (left: T, right: T) => boolean,
|
|
85
|
-
): number {
|
|
86
|
-
if (!sameBundle) {
|
|
87
|
-
return start;
|
|
88
|
-
}
|
|
89
|
-
while (start > 0 && sameBundle(items[start - 1]!, items[start]!)) {
|
|
90
|
-
start -= 1;
|
|
91
|
-
}
|
|
92
|
-
return start;
|
|
93
|
-
}
|
package/src/dream-promotion.ts
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import fsp from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import { getHashBackendName, hashBytes } from "./markdown-hash.js";
|
|
6
|
-
import type { LoggerLike, PluginConfig } from "./types.js";
|
|
7
|
-
|
|
8
|
-
const DEFAULT_DEBOUNCE_MS = 150;
|
|
9
|
-
const DEFAULT_MIN_SCORE = 0.6;
|
|
10
|
-
const DEFAULT_MIN_RECALL_COUNT = 2;
|
|
11
|
-
const DEFAULT_MIN_UNIQUE_QUERIES = 2;
|
|
12
|
-
const DREAM_PROMOTION_VERSION = 1;
|
|
13
|
-
const DREAM_SOURCE_KIND = "dream";
|
|
14
|
-
|
|
15
|
-
type Disposable = { close(): void };
|
|
16
|
-
|
|
17
|
-
interface RpcLike {
|
|
18
|
-
call<T>(method: string, params: unknown): Promise<T>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type RpcGetterLike = () => Promise<RpcLike>;
|
|
22
|
-
|
|
23
|
-
interface FsWatcherLike extends Disposable {
|
|
24
|
-
on(event: "error", handler: (error: Error) => void): void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface FsApi {
|
|
28
|
-
readFile(file: string): Promise<Uint8Array>;
|
|
29
|
-
stat(file: string): Promise<{ size: number; mtimeMs: number }>;
|
|
30
|
-
watch(dir: string, onChange: (event: string, filename: string | Buffer | null) => void): FsWatcherLike;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DreamPromotionHandle {
|
|
34
|
-
start(): Promise<void>;
|
|
35
|
-
refresh(): Promise<void>;
|
|
36
|
-
stop(): Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface DreamPromotionCandidate {
|
|
40
|
-
text: string;
|
|
41
|
-
score: number;
|
|
42
|
-
recallCount: number;
|
|
43
|
-
uniqueQueries: number;
|
|
44
|
-
section: string;
|
|
45
|
-
line: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface DreamPromotionEntry extends DreamPromotionCandidate {
|
|
49
|
-
sourceLine: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface DreamPromotionParams {
|
|
53
|
-
userId: string;
|
|
54
|
-
sourceDoc: string;
|
|
55
|
-
sourceRoot: string;
|
|
56
|
-
sourcePath: string;
|
|
57
|
-
sourceKind: string;
|
|
58
|
-
fileHash: string;
|
|
59
|
-
sourceSize: number;
|
|
60
|
-
sourceMtimeMs: number;
|
|
61
|
-
ingestVersion: number;
|
|
62
|
-
hashBackend: string;
|
|
63
|
-
entries: DreamPromotionEntry[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface DreamPromotionResult {
|
|
67
|
-
promoted?: number;
|
|
68
|
-
rejected?: number;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface DreamFileState {
|
|
72
|
-
size: number;
|
|
73
|
-
mtimeMs: number;
|
|
74
|
-
fileHash: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
interface DreamPromotionState {
|
|
78
|
-
watching: boolean;
|
|
79
|
-
dirty: boolean;
|
|
80
|
-
timer: ReturnType<typeof setTimeout> | null;
|
|
81
|
-
watcher: FsWatcherLike | null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function createDreamPromotionHandle(
|
|
85
|
-
cfg: PluginConfig,
|
|
86
|
-
getRpc: RpcGetterLike,
|
|
87
|
-
logger: LoggerLike = console,
|
|
88
|
-
fsApi: FsApi = createRealFsApi(),
|
|
89
|
-
): DreamPromotionHandle {
|
|
90
|
-
const diaryPath = normalizeDiaryPath(cfg.dreamPromotionDiaryPath);
|
|
91
|
-
const userId = cfg.dreamPromotionUserId?.trim() ?? "";
|
|
92
|
-
if (cfg.dreamPromotionEnabled !== true || !diaryPath || !userId) {
|
|
93
|
-
return {
|
|
94
|
-
async start() {},
|
|
95
|
-
async refresh() {},
|
|
96
|
-
async stop() {},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const state: DreamPromotionState = {
|
|
101
|
-
watching: false,
|
|
102
|
-
dirty: false,
|
|
103
|
-
timer: null,
|
|
104
|
-
watcher: null,
|
|
105
|
-
};
|
|
106
|
-
let lastFileState: DreamFileState | null = null;
|
|
107
|
-
const debounceMs = cfg.dreamPromotionDebounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
async start(): Promise<void> {
|
|
111
|
-
if (state.watching) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
state.watching = true;
|
|
115
|
-
await refreshDiary();
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
async refresh(): Promise<void> {
|
|
119
|
-
await refreshDiary();
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
async stop(): Promise<void> {
|
|
123
|
-
state.watching = false;
|
|
124
|
-
if (state.timer) {
|
|
125
|
-
clearTimeout(state.timer);
|
|
126
|
-
state.timer = null;
|
|
127
|
-
}
|
|
128
|
-
if (state.watcher) {
|
|
129
|
-
state.watcher.close();
|
|
130
|
-
state.watcher = null;
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
async function refreshDiary(): Promise<void> {
|
|
136
|
-
if (!state.watching) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (state.timer) {
|
|
140
|
-
state.dirty = true;
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
state.timer = setTimeout(() => {
|
|
144
|
-
state.timer = null;
|
|
145
|
-
void scanDiary().catch((error) => {
|
|
146
|
-
logger.warn?.(`[dream-promotion] refresh failed for ${diaryPath}: ${formatError(error)}`);
|
|
147
|
-
});
|
|
148
|
-
}, debounceMs);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function scanDiary(): Promise<void> {
|
|
152
|
-
if (!state.watching) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
await ensureWatcher();
|
|
156
|
-
|
|
157
|
-
const stat = await safeStat(diaryPath);
|
|
158
|
-
if (!stat) {
|
|
159
|
-
lastFileState = null;
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (lastFileState && lastFileState.size === stat.size && lastFileState.mtimeMs === stat.mtimeMs) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const bytes = await safeReadFile(diaryPath);
|
|
168
|
-
if (!bytes) {
|
|
169
|
-
lastFileState = null;
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const fileHash = hashBytes(bytes);
|
|
174
|
-
if (lastFileState && lastFileState.fileHash === fileHash) {
|
|
175
|
-
lastFileState = {
|
|
176
|
-
size: stat.size,
|
|
177
|
-
mtimeMs: stat.mtimeMs,
|
|
178
|
-
fileHash,
|
|
179
|
-
};
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const text = textDecoder.decode(bytes);
|
|
184
|
-
const candidates = parseDreamPromotionCandidates(text);
|
|
185
|
-
if (candidates.length === 0) {
|
|
186
|
-
lastFileState = {
|
|
187
|
-
size: stat.size,
|
|
188
|
-
mtimeMs: stat.mtimeMs,
|
|
189
|
-
fileHash,
|
|
190
|
-
};
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const rpc = await getRpc();
|
|
195
|
-
const params: DreamPromotionParams = {
|
|
196
|
-
userId,
|
|
197
|
-
sourceDoc: diaryPath,
|
|
198
|
-
sourceRoot: path.dirname(diaryPath),
|
|
199
|
-
sourcePath: path.basename(diaryPath),
|
|
200
|
-
sourceKind: DREAM_SOURCE_KIND,
|
|
201
|
-
fileHash,
|
|
202
|
-
sourceSize: stat.size,
|
|
203
|
-
sourceMtimeMs: stat.mtimeMs,
|
|
204
|
-
ingestVersion: DREAM_PROMOTION_VERSION,
|
|
205
|
-
hashBackend: getHashBackendName(),
|
|
206
|
-
entries: candidates.map((candidate, index) => ({
|
|
207
|
-
...candidate,
|
|
208
|
-
sourceLine: candidate.line,
|
|
209
|
-
line: index + 1,
|
|
210
|
-
})),
|
|
211
|
-
};
|
|
212
|
-
await rpc.call<DreamPromotionResult>("promote_dream_entries", params);
|
|
213
|
-
|
|
214
|
-
lastFileState = {
|
|
215
|
-
size: stat.size,
|
|
216
|
-
mtimeMs: stat.mtimeMs,
|
|
217
|
-
fileHash,
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
if (state.dirty) {
|
|
221
|
-
state.dirty = false;
|
|
222
|
-
await refreshDiary();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async function safeStat(filePath: string): Promise<{ size: number; mtimeMs: number } | null> {
|
|
227
|
-
try {
|
|
228
|
-
return await fsApi.stat(filePath);
|
|
229
|
-
} catch {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function safeReadFile(filePath: string): Promise<Uint8Array | null> {
|
|
235
|
-
try {
|
|
236
|
-
return await fsApi.readFile(filePath);
|
|
237
|
-
} catch {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function ensureWatcher(): Promise<void> {
|
|
243
|
-
if (state.watcher) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const parentDir = path.dirname(diaryPath);
|
|
247
|
-
try {
|
|
248
|
-
const watcher = fsApi.watch(parentDir, (_event, filename) => {
|
|
249
|
-
if (filename && path.basename(String(filename)) !== path.basename(diaryPath)) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
state.dirty = true;
|
|
253
|
-
void refreshDiary();
|
|
254
|
-
});
|
|
255
|
-
watcher.on("error", (error) => {
|
|
256
|
-
logger.warn?.(`[dream-promotion] watch error for ${parentDir}: ${formatError(error)}`);
|
|
257
|
-
});
|
|
258
|
-
state.watcher = watcher;
|
|
259
|
-
} catch (error) {
|
|
260
|
-
logger.warn?.(`[dream-promotion] watch unavailable for ${parentDir}: ${formatError(error)}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function promoteDreamDiaryFile(
|
|
266
|
-
rpc: RpcLike,
|
|
267
|
-
opts: {
|
|
268
|
-
userId: string;
|
|
269
|
-
diaryPath: string;
|
|
270
|
-
text?: string;
|
|
271
|
-
fileHash?: string;
|
|
272
|
-
sourceSize?: number;
|
|
273
|
-
sourceMtimeMs?: number;
|
|
274
|
-
},
|
|
275
|
-
): Promise<DreamPromotionResult> {
|
|
276
|
-
const diaryPath = normalizeDiaryPath(opts.diaryPath);
|
|
277
|
-
if (!diaryPath) {
|
|
278
|
-
throw new Error("dream diary path is required");
|
|
279
|
-
}
|
|
280
|
-
const userId = opts.userId.trim();
|
|
281
|
-
if (!userId) {
|
|
282
|
-
throw new Error("user id is required");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let text = opts.text;
|
|
286
|
-
let fileHash = opts.fileHash;
|
|
287
|
-
let sourceSize = opts.sourceSize;
|
|
288
|
-
let sourceMtimeMs = opts.sourceMtimeMs;
|
|
289
|
-
if (text == null) {
|
|
290
|
-
const bytes = await fsp.readFile(diaryPath);
|
|
291
|
-
text = textDecoder.decode(bytes);
|
|
292
|
-
fileHash = fileHash ?? hashBytes(bytes);
|
|
293
|
-
const stat = await fsp.stat(diaryPath);
|
|
294
|
-
sourceSize = sourceSize ?? stat.size;
|
|
295
|
-
sourceMtimeMs = sourceMtimeMs ?? stat.mtimeMs;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const candidates = parseDreamPromotionCandidates(text);
|
|
299
|
-
return await rpc.call<DreamPromotionResult>("promote_dream_entries", {
|
|
300
|
-
userId,
|
|
301
|
-
sourceDoc: diaryPath,
|
|
302
|
-
sourceRoot: path.dirname(diaryPath),
|
|
303
|
-
sourcePath: path.basename(diaryPath),
|
|
304
|
-
sourceKind: DREAM_SOURCE_KIND,
|
|
305
|
-
fileHash: fileHash ?? "",
|
|
306
|
-
sourceSize: sourceSize ?? 0,
|
|
307
|
-
sourceMtimeMs: sourceMtimeMs ?? 0,
|
|
308
|
-
ingestVersion: DREAM_PROMOTION_VERSION,
|
|
309
|
-
hashBackend: getHashBackendName(),
|
|
310
|
-
entries: candidates.map((candidate, index) => ({
|
|
311
|
-
...candidate,
|
|
312
|
-
sourceLine: candidate.line,
|
|
313
|
-
line: index + 1,
|
|
314
|
-
})),
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function parseDreamPromotionCandidates(text: string): DreamPromotionCandidate[] {
|
|
319
|
-
const candidates: DreamPromotionCandidate[] = [];
|
|
320
|
-
const lines = text.split("\n");
|
|
321
|
-
let inFence = false;
|
|
322
|
-
let activeSection = "";
|
|
323
|
-
|
|
324
|
-
for (let index = 0; index < lines.length; index++) {
|
|
325
|
-
const line = lines[index] ?? "";
|
|
326
|
-
const trimmed = line.trimStart();
|
|
327
|
-
if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
|
|
328
|
-
inFence = !inFence;
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (inFence) {
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const heading = parseHeading(trimmed);
|
|
336
|
-
if (heading) {
|
|
337
|
-
activeSection = heading;
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (!isPromotionSection(activeSection)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const bullet = parseBulletCandidate(line);
|
|
346
|
-
if (!bullet) {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
const metadata = parseTrailingMetadata(bullet.body);
|
|
350
|
-
if (!metadata) {
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const textValue = bullet.body.slice(0, metadata.bodyStart).trim();
|
|
355
|
-
if (!textValue) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
candidates.push({
|
|
360
|
-
text: textValue,
|
|
361
|
-
score: metadata.score,
|
|
362
|
-
recallCount: metadata.recallCount,
|
|
363
|
-
uniqueQueries: metadata.uniqueQueries,
|
|
364
|
-
section: activeSection,
|
|
365
|
-
line: index + 1,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return candidates;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function parseHeading(value: string): string | null {
|
|
373
|
-
const match = /^(#{2,6})\s+(.+)$/.exec(value);
|
|
374
|
-
if (!match) {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
return normalizeSectionName(match[2] ?? "");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function isPromotionSection(section: string): boolean {
|
|
381
|
-
return section.includes("deep sleep") || section.includes("promot") || section.includes("dream");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function parseBulletCandidate(line: string): { body: string } | null {
|
|
385
|
-
const match = /^\s*[-*+]\s+(.+)$/.exec(line);
|
|
386
|
-
if (!match) {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
return { body: match[1] ?? "" };
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function parseTrailingMetadata(body: string): { bodyStart: number; score: number; recallCount: number; uniqueQueries: number } | null {
|
|
393
|
-
const trimmed = body.trimEnd();
|
|
394
|
-
if (!trimmed.endsWith("}")) {
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const open = trimmed.lastIndexOf("{");
|
|
399
|
-
if (open < 0) {
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const metadataText = trimmed.slice(open + 1, -1).trim();
|
|
404
|
-
const text = trimmed.slice(0, open).trimEnd();
|
|
405
|
-
if (!metadataText || !text) {
|
|
406
|
-
return null;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const fields = new Map<string, string>();
|
|
410
|
-
for (const token of metadataText.split(/[,\s]+/)) {
|
|
411
|
-
if (!token) {
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
const equals = token.indexOf("=");
|
|
415
|
-
if (equals <= 0) {
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
const key = token.slice(0, equals).trim().toLowerCase();
|
|
419
|
-
const value = token.slice(equals + 1).trim();
|
|
420
|
-
if (key && value) {
|
|
421
|
-
fields.set(key, value);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const score = parseNumber(fields.get("score"));
|
|
426
|
-
const recallCount = parseInteger(fields.get("recall") ?? fields.get("recallcount"));
|
|
427
|
-
const uniqueQueries = parseInteger(fields.get("unique") ?? fields.get("uniquequeries"));
|
|
428
|
-
if (score == null || recallCount == null || uniqueQueries == null) {
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return {
|
|
433
|
-
bodyStart: text.length,
|
|
434
|
-
score,
|
|
435
|
-
recallCount,
|
|
436
|
-
uniqueQueries,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function parseNumber(value: string | undefined): number | null {
|
|
441
|
-
if (!value) {
|
|
442
|
-
return null;
|
|
443
|
-
}
|
|
444
|
-
const parsed = Number.parseFloat(value);
|
|
445
|
-
if (!Number.isFinite(parsed)) {
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
return parsed;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function parseInteger(value: string | undefined): number | null {
|
|
452
|
-
if (!value) {
|
|
453
|
-
return null;
|
|
454
|
-
}
|
|
455
|
-
const parsed = Number.parseInt(value, 10);
|
|
456
|
-
if (!Number.isFinite(parsed)) {
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
return parsed;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function normalizeSectionName(value: string): string {
|
|
463
|
-
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function normalizeDiaryPath(value?: string): string {
|
|
467
|
-
const trimmed = value?.trim();
|
|
468
|
-
if (!trimmed) {
|
|
469
|
-
return "";
|
|
470
|
-
}
|
|
471
|
-
return path.resolve(trimmed);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function createRealFsApi(): FsApi {
|
|
475
|
-
return {
|
|
476
|
-
readFile: async (file: string) => fsp.readFile(file),
|
|
477
|
-
stat: async (file: string) => {
|
|
478
|
-
const stat = await fsp.stat(file);
|
|
479
|
-
return { size: stat.size, mtimeMs: stat.mtimeMs };
|
|
480
|
-
},
|
|
481
|
-
watch: (dir: string, onChange: (event: string, filename: string | Buffer | null) => void) => fs.watch(dir, onChange),
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function formatError(error: unknown): string {
|
|
486
|
-
if (error instanceof Error) {
|
|
487
|
-
return error.message;
|
|
488
|
-
}
|
|
489
|
-
return String(error);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const textDecoder = new TextDecoder();
|
package/src/dream-routing.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const DREAM_COLLECTION_PREFIX = "dream:";
|
|
2
|
-
|
|
3
|
-
const DREAM_PATTERN_RULES: Array<{ label: string; patterns: RegExp[] }> = [
|
|
4
|
-
{
|
|
5
|
-
label: "dream",
|
|
6
|
-
patterns: [
|
|
7
|
-
/\bdream(?:s|ed|ing)?\b/i,
|
|
8
|
-
/\btell\s+me\s+about\s+(?:your\s+)?dreams?\b/i,
|
|
9
|
-
/\bwhat\s+did\s+i\s+dream\s+about\b/i,
|
|
10
|
-
/\bwhat\s+was\s+i\s+dreaming\s+about\b/i,
|
|
11
|
-
],
|
|
12
|
-
},
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
const DREAM_MATCHED_PATTERNS: string[] = ["dream"];
|
|
16
|
-
const EMPTY_MATCHED_PATTERNS: string[] = [];
|
|
17
|
-
|
|
18
|
-
export interface DreamQuerySignal {
|
|
19
|
-
active: boolean;
|
|
20
|
-
matchedPatterns: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function detectDreamQuerySignal(queryText: string): DreamQuerySignal {
|
|
24
|
-
for (const rule of DREAM_PATTERN_RULES) {
|
|
25
|
-
if (rule.patterns.some((pattern) => pattern.test(queryText))) {
|
|
26
|
-
return {
|
|
27
|
-
active: true,
|
|
28
|
-
matchedPatterns: DREAM_MATCHED_PATTERNS,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
active: false,
|
|
34
|
-
matchedPatterns: EMPTY_MATCHED_PATTERNS,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function resolveDreamCollection(userId: string): string {
|
|
39
|
-
return `${DREAM_COLLECTION_PREFIX}${userId.trim()}`;
|
|
40
|
-
}
|
package/src/durable-namespace.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const SESSION_KEY_NAMESPACE_PREFIX = "session-key:";
|
|
2
|
-
const AGENT_ID_NAMESPACE_PREFIX = "agent-id:";
|
|
3
|
-
|
|
4
|
-
export function resolveDurableNamespace(params: {
|
|
5
|
-
userId?: string;
|
|
6
|
-
sessionKey?: string;
|
|
7
|
-
agentId?: string;
|
|
8
|
-
fallback?: string;
|
|
9
|
-
}): string {
|
|
10
|
-
const explicitUserId = firstNonEmpty(params.userId);
|
|
11
|
-
if (explicitUserId) {
|
|
12
|
-
return explicitUserId;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const sessionKey = firstNonEmpty(params.sessionKey);
|
|
16
|
-
if (sessionKey) {
|
|
17
|
-
return `${SESSION_KEY_NAMESPACE_PREFIX}${sessionKey}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const agentId = firstNonEmpty(params.agentId);
|
|
21
|
-
if (agentId) {
|
|
22
|
-
return `${AGENT_ID_NAMESPACE_PREFIX}${agentId}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return firstNonEmpty(params.fallback) ?? "default";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function firstNonEmpty(value: string | undefined): string | undefined {
|
|
29
|
-
if (typeof value !== "string") {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
const trimmed = value.trim();
|
|
33
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
34
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { definePluginEntry, type OpenClawPluginApi } 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
|
-
import type { PluginConfig, SearchResult } from "./types.js";
|
|
12
|
-
|
|
13
|
-
export default definePluginEntry({
|
|
14
|
-
id: "libravdb-memory",
|
|
15
|
-
name: "LibraVDB Memory",
|
|
16
|
-
description: "Persistent vector memory with three-tier hybrid scoring",
|
|
17
|
-
kind: ["memory", "context-engine"],
|
|
18
|
-
|
|
19
|
-
register(api: OpenClawPluginApi) {
|
|
20
|
-
const cfg = api.pluginConfig as PluginConfig;
|
|
21
|
-
const recallCache = createRecallCache<SearchResult>();
|
|
22
|
-
const runtime = createPluginRuntime(cfg, api.logger ?? console);
|
|
23
|
-
const markdownIngestion = createMarkdownIngestionHandle(cfg, runtime.getRpc, api.logger ?? console);
|
|
24
|
-
const dreamPromotion = createDreamPromotionHandle(cfg, runtime.getRpc, api.logger ?? console);
|
|
25
|
-
|
|
26
|
-
void markdownIngestion.start().catch((error) => {
|
|
27
|
-
api.logger?.warn?.(`LibraVDB markdown ingestion failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
28
|
-
});
|
|
29
|
-
void dreamPromotion.start().catch((error) => {
|
|
30
|
-
api.logger?.warn?.(`LibraVDB dream promotion failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
registerMemoryCli(api, runtime, cfg, api.logger ?? console);
|
|
34
|
-
api.registerContextEngine("libravdb-memory", () =>
|
|
35
|
-
buildContextEngineFactory(runtime.getRpc, cfg, recallCache),
|
|
36
|
-
);
|
|
37
|
-
api.registerMemoryPromptSection(buildMemoryPromptSection(runtime.getRpc, cfg, recallCache));
|
|
38
|
-
api.registerMemoryRuntime?.(buildMemoryRuntimeBridge(runtime.getRpc, cfg));
|
|
39
|
-
api.on("before_reset", createBeforeResetHook(runtime, api.logger ?? console));
|
|
40
|
-
api.on("session_end", createSessionEndHook(runtime, api.logger ?? console));
|
|
41
|
-
api.on("gateway_stop", async () => {
|
|
42
|
-
await dreamPromotion.stop();
|
|
43
|
-
await markdownIngestion.stop();
|
|
44
|
-
await runtime.shutdown();
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
});
|