@xdarkicex/openclaw-memory-libravdb 1.3.11 → 1.3.12

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/src/scoring.ts CHANGED
@@ -12,16 +12,52 @@ interface HybridOptions {
12
12
  userId: string;
13
13
  }
14
14
 
15
+ interface Section7Options {
16
+ queryText: string;
17
+ sessionId: string;
18
+ userId: string;
19
+ k1?: number;
20
+ k2?: number;
21
+ theta1?: number;
22
+ kappa?: number;
23
+ authorityRecencyLambda?: number;
24
+ authorityRecencyWeight?: number;
25
+ authorityFrequencyWeight?: number;
26
+ authorityAuthoredWeight?: number;
27
+ nowMs?: number;
28
+ }
29
+
30
+ interface HopOptions {
31
+ etaHop?: number;
32
+ thetaHop?: number;
33
+ }
34
+
35
+ export function mergeSection7VariantCandidates(
36
+ ranked: SearchResult[],
37
+ hopExpanded: SearchResult[],
38
+ ): SearchResult[] {
39
+ const byID = new Map<string, SearchResult>();
40
+ for (const item of [...ranked, ...hopExpanded]) {
41
+ const existing = byID.get(item.id);
42
+ if (!existing || (item.finalScore ?? 0) > (existing.finalScore ?? 0)) {
43
+ byID.set(item.id, item);
44
+ }
45
+ }
46
+ return [...byID.values()].sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0));
47
+ }
48
+
15
49
  export function scoreCandidates(items: SearchResult[], opts: HybridOptions): SearchResult[] {
16
50
  const now = Date.now();
17
- const alpha = opts.alpha ?? 0.7;
18
- const beta = opts.beta ?? 0.2;
19
- const gamma = opts.gamma ?? 0.1;
20
- const delta = opts.delta ?? 0.5;
51
+ const { alpha, beta, gamma } = normalizeWeights(
52
+ opts.alpha ?? 0.7,
53
+ opts.beta ?? 0.2,
54
+ opts.gamma ?? 0.1,
55
+ );
56
+ const delta = clamp01(opts.delta ?? 0.5);
21
57
  // Lambda units are per-second decay constants.
22
- const recencyLambdaSession = opts.recencyLambdaSession ?? 0.0001;
23
- const recencyLambdaUser = opts.recencyLambdaUser ?? 0.00001;
24
- const recencyLambdaGlobal = opts.recencyLambdaGlobal ?? 0.000002;
58
+ const recencyLambdaSession = Math.max(0, opts.recencyLambdaSession ?? 0.0001);
59
+ const recencyLambdaUser = Math.max(0, opts.recencyLambdaUser ?? 0.00001);
60
+ const recencyLambdaGlobal = Math.max(0, opts.recencyLambdaGlobal ?? 0.000002);
25
61
 
26
62
  return items
27
63
  .map((item) => {
@@ -36,8 +72,9 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
36
72
  item.metadata.sessionId === opts.sessionId ? 1.0
37
73
  : item.metadata.userId === opts.userId ? 0.6
38
74
  : 0.3;
75
+ const similarity = clamp01(item.score);
39
76
  const baseScore =
40
- alpha * item.score +
77
+ alpha * similarity +
41
78
  beta * recency +
42
79
  gamma * scopeBoost;
43
80
  const rawDecayRate =
@@ -47,7 +84,7 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
47
84
  item.metadata.type === "summary"
48
85
  ? 1.0 - delta * decayRate
49
86
  : 1.0;
50
- const finalScore = baseScore * quality;
87
+ const finalScore = clamp01(baseScore * quality);
51
88
 
52
89
  return {
53
90
  ...item,
@@ -56,3 +93,220 @@ export function scoreCandidates(items: SearchResult[], opts: HybridOptions): Sea
56
93
  })
57
94
  .sort((a, b) => (b.finalScore ?? 0) - (a.finalScore ?? 0));
58
95
  }
96
+
97
+ export function rankSection7VariantCandidates(items: SearchResult[], opts: Section7Options): SearchResult[] {
98
+ const now = opts.nowMs ?? Date.now();
99
+ const k1 = Math.max(1, Math.floor(opts.k1 ?? 16));
100
+ const k2 = Math.max(1, Math.floor(opts.k2 ?? 8));
101
+ const theta1 = clampSimilarity(opts.theta1 ?? 0.2);
102
+ const kappa = Math.max(0, opts.kappa ?? 0.3);
103
+ const { alpha: alphaR, beta: alphaF, gamma: alphaA } = normalizeWeights(
104
+ opts.authorityRecencyWeight ?? 0.5,
105
+ opts.authorityFrequencyWeight ?? 0.2,
106
+ opts.authorityAuthoredWeight ?? 0.3,
107
+ );
108
+ const authorityRecencyLambda = Math.max(0, opts.authorityRecencyLambda ?? 0.00001);
109
+
110
+ const deduped = dedupeCandidates(items);
111
+ const coarseRaw = [...deduped]
112
+ .sort((left, right) => similarity(right) - similarity(left))
113
+ .slice(0, k1);
114
+ const coarseFiltered = coarseRaw.filter((item) => similarity(item) >= theta1);
115
+ const maxAccessCount = coarseFiltered.reduce((max, item) => Math.max(max, accessCount(item)), 0);
116
+ const keywords = extractKeywords(opts.queryText);
117
+
118
+ return coarseFiltered
119
+ .map((item) => {
120
+ const omega = authorityWeight(item, {
121
+ now,
122
+ authorityRecencyLambda,
123
+ alphaR,
124
+ alphaF,
125
+ alphaA,
126
+ maxAccessCount,
127
+ });
128
+ const sim = Math.max(similarity(item), 0);
129
+ const keywordCoverage = normalizedKeywordCoverage(keywords, item.text);
130
+ const finalScore = omega * sim * ((1 + kappa * keywordCoverage) / (1 + kappa));
131
+
132
+ return {
133
+ ...item,
134
+ finalScore: clamp01(finalScore),
135
+ };
136
+ })
137
+ .sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0))
138
+ .slice(0, Math.min(k2, coarseFiltered.length));
139
+ }
140
+
141
+ export function expandSection7HopCandidates(
142
+ ranked: SearchResult[],
143
+ authoredVariantRecords: SearchResult[],
144
+ opts: HopOptions,
145
+ ): SearchResult[] {
146
+ const etaHop = clampOpenUnit(opts.etaHop ?? 0.5);
147
+ const thetaHop = clamp01(opts.thetaHop ?? 0.15);
148
+ const rankedIDs = new Set(ranked.map((item) => item.id));
149
+ const authoredByID = new Map(authoredVariantRecords.map((item) => [item.id, item] as const));
150
+ const bestScores = new Map<string, number>();
151
+
152
+ for (const parent of ranked) {
153
+ const parentScore = clamp01(parent.finalScore ?? 0);
154
+ for (const targetID of hopTargets(parent)) {
155
+ if (rankedIDs.has(targetID)) {
156
+ continue;
157
+ }
158
+ if (!authoredByID.has(targetID)) {
159
+ continue;
160
+ }
161
+ const candidateScore = etaHop * parentScore;
162
+ if (candidateScore > (bestScores.get(targetID) ?? -1)) {
163
+ bestScores.set(targetID, candidateScore);
164
+ }
165
+ }
166
+ }
167
+
168
+ return [...bestScores.entries()]
169
+ .filter(([, score]) => score >= thetaHop)
170
+ .map(([id, score]) => ({
171
+ ...authoredByID.get(id)!,
172
+ finalScore: score,
173
+ }))
174
+ .sort((left, right) => (right.finalScore ?? 0) - (left.finalScore ?? 0));
175
+ }
176
+
177
+ function clamp01(value: number): number {
178
+ return Math.min(1, Math.max(0, value));
179
+ }
180
+
181
+ function clampSimilarity(value: number): number {
182
+ return Math.min(1, Math.max(-1, value));
183
+ }
184
+
185
+ function clampOpenUnit(value: number): number {
186
+ return Math.min(0.999999, Math.max(0.000001, value));
187
+ }
188
+
189
+ function normalizeWeights(alpha: number, beta: number, gamma: number): { alpha: number; beta: number; gamma: number } {
190
+ alpha = clamp01(alpha);
191
+ beta = clamp01(beta);
192
+ gamma = clamp01(gamma);
193
+
194
+ const sum = alpha + beta + gamma;
195
+ if (sum <= 0) {
196
+ return { alpha: 0.7, beta: 0.2, gamma: 0.1 };
197
+ }
198
+
199
+ return {
200
+ alpha: alpha / sum,
201
+ beta: beta / sum,
202
+ gamma: gamma / sum,
203
+ };
204
+ }
205
+
206
+ function dedupeCandidates(items: SearchResult[]): SearchResult[] {
207
+ const seen = new Set<string>();
208
+ const out: SearchResult[] = [];
209
+ for (const item of items) {
210
+ const key = `${typeof item.metadata.collection === "string" ? item.metadata.collection : ""}::${item.id}`;
211
+ if (seen.has(key)) {
212
+ continue;
213
+ }
214
+ seen.add(key);
215
+ out.push(item);
216
+ }
217
+ return out;
218
+ }
219
+
220
+ function similarity(item: SearchResult): number {
221
+ return clampSimilarity(typeof item.score === "number" ? item.score : 0);
222
+ }
223
+
224
+ function accessCount(item: SearchResult): number {
225
+ const raw = item.metadata.access_count;
226
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : 0;
227
+ }
228
+
229
+ function authorityWeight(
230
+ item: SearchResult,
231
+ opts: {
232
+ now: number;
233
+ authorityRecencyLambda: number;
234
+ alphaR: number;
235
+ alphaF: number;
236
+ alphaA: number;
237
+ maxAccessCount: number;
238
+ },
239
+ ): number {
240
+ const ts = typeof item.metadata.ts === "number" ? item.metadata.ts : opts.now;
241
+ const ageSeconds = Math.max(0, opts.now - ts) / 1000;
242
+ const recency = Math.exp(-opts.authorityRecencyLambda * ageSeconds);
243
+ const frequency = normalizedFrequency(accessCount(item), opts.maxAccessCount);
244
+ const authoredAuthority = clamp01(
245
+ typeof item.metadata.authority === "number"
246
+ ? item.metadata.authority
247
+ : item.metadata.authored === true
248
+ ? 1
249
+ : 0,
250
+ );
251
+ return clamp01(
252
+ opts.alphaR * recency +
253
+ opts.alphaF * frequency +
254
+ opts.alphaA * authoredAuthority,
255
+ );
256
+ }
257
+
258
+ function normalizedFrequency(accessCount: number, maxAccessCount: number): number {
259
+ if (accessCount <= 0 || maxAccessCount <= 0) {
260
+ return 0;
261
+ }
262
+ return Math.log(1 + accessCount) / Math.log(1 + maxAccessCount + 1);
263
+ }
264
+
265
+ function extractKeywords(text: string): string[] {
266
+ const tokens = normalizeTerms(text);
267
+ const seen = new Set<string>();
268
+ const keywords: string[] = [];
269
+ for (const token of tokens) {
270
+ if (token.length < 3 || seen.has(token)) {
271
+ continue;
272
+ }
273
+ seen.add(token);
274
+ keywords.push(token);
275
+ }
276
+ return keywords;
277
+ }
278
+
279
+ function normalizedKeywordCoverage(keywords: string[], text: string): number {
280
+ if (keywords.length === 0) {
281
+ return 0;
282
+ }
283
+ const docTerms = new Set(normalizeTerms(text));
284
+ let matches = 0;
285
+ for (const keyword of keywords) {
286
+ if (docTerms.has(keyword)) {
287
+ matches += 1;
288
+ }
289
+ }
290
+ return matches / Math.max(keywords.length, 1);
291
+ }
292
+
293
+ function normalizeTerms(text: string): string[] {
294
+ return text
295
+ .toLowerCase()
296
+ .split(/[^a-z0-9_]+/i)
297
+ .filter((term) => term.length > 0);
298
+ }
299
+
300
+ function hopTargets(item: SearchResult): string[] {
301
+ const raw = item.metadata.hop_targets;
302
+ if (Array.isArray(raw)) {
303
+ return raw.filter((target): target is string => typeof target === "string" && target.length > 0);
304
+ }
305
+ if (typeof raw === "string") {
306
+ return raw
307
+ .split(",")
308
+ .map((part) => part.trim())
309
+ .filter((part) => part.length > 0);
310
+ }
311
+ return [];
312
+ }
package/src/tokens.ts CHANGED
@@ -12,7 +12,7 @@ export function fitPromptBudget(items: SearchResult[], budget: number): SearchRe
12
12
  for (const item of items) {
13
13
  const cost = estimateTokens(item.text);
14
14
  if (used + cost > budget) {
15
- continue;
15
+ break;
16
16
  }
17
17
  selected.push(item);
18
18
  used += cost;
package/src/types.ts CHANGED
@@ -37,7 +37,23 @@ export interface PluginConfig {
37
37
  recencyLambdaUser?: number;
38
38
  recencyLambdaGlobal?: number;
39
39
  tokenBudgetFraction?: number;
40
+ authoredHardBudgetFraction?: number;
41
+ authoredSoftBudgetFraction?: number;
42
+ section7StartupTokenBudgetTokens?: number;
43
+ continuityMinTurns?: number;
44
+ continuityTailBudgetTokens?: number;
45
+ continuityPriorContextTokens?: number;
40
46
  compactThreshold?: number;
47
+ section7CoarseTopK?: number;
48
+ section7SecondPassTopK?: number;
49
+ section7Theta1?: number;
50
+ section7Kappa?: number;
51
+ section7HopEta?: number;
52
+ section7HopThreshold?: number;
53
+ section7AuthorityRecencyLambda?: number;
54
+ section7AuthorityRecencyWeight?: number;
55
+ section7AuthorityFrequencyWeight?: number;
56
+ section7AuthorityAuthoredWeight?: number;
41
57
  ollamaUrl?: string;
42
58
  compactModel?: string;
43
59
  rpcTimeoutMs?: number;
@@ -75,6 +91,19 @@ export interface SearchResult {
75
91
  sessionId?: string;
76
92
  userId?: string;
77
93
  role?: string;
94
+ source_doc?: string;
95
+ node_kind?: string;
96
+ ordinal?: number;
97
+ tier?: number;
98
+ authored?: boolean;
99
+ authority?: number;
100
+ access_count?: number;
101
+ collection?: string;
102
+ hop_targets?: string[] | string;
103
+ token_estimate?: number;
104
+ continuity_tail?: boolean;
105
+ continuity_base?: boolean;
106
+ continuity_bundle_id?: string;
78
107
  [key: string]: unknown;
79
108
  };
80
109
  finalScore?: number;
@@ -109,8 +138,10 @@ export interface RpcCallOptions {
109
138
  export interface RecallCacheEntry<T = unknown> {
110
139
  userId: string;
111
140
  queryText: string;
112
- userHits: T[];
113
- globalHits: T[];
141
+ durableVariantHits: T[];
142
+ userHits?: T[];
143
+ globalHits?: T[];
144
+ authoredVariantHits?: T[];
114
145
  }
115
146
 
116
147
  export interface RecallCache<T = unknown> {