@xdarkicex/openclaw-memory-libravdb 1.4.11 → 1.4.13

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.
@@ -17,6 +17,18 @@ type OpenClawCompatibleAssembleResult = {
17
17
  systemPromptAddition: string;
18
18
  debug?: AssembleContextInternalResponse["debug"];
19
19
  };
20
+ type OpenClawCompatibleCompactResult = {
21
+ ok: boolean;
22
+ compacted: boolean;
23
+ reason?: string;
24
+ result?: {
25
+ summary?: string;
26
+ firstKeptEntryId?: string;
27
+ tokensBefore: number;
28
+ tokensAfter?: number;
29
+ details?: unknown;
30
+ };
31
+ };
20
32
  export declare function normalizeKernelMessage(message: {
21
33
  role: string;
22
34
  content: unknown;
@@ -77,7 +89,7 @@ export declare function buildContextEngineFactory(runtime: PluginRuntime, cfg: P
77
89
  force?: boolean;
78
90
  targetSize?: number;
79
91
  tokenBudget?: number;
80
- }): Promise<any>;
92
+ }): Promise<OpenClawCompatibleCompactResult>;
81
93
  afterTurn(args: {
82
94
  sessionId: string;
83
95
  sessionKey?: string;
@@ -1,3 +1,35 @@
1
+ const APPROX_CHARS_PER_TOKEN = 4;
2
+ const ASSEMBLE_BUDGET_HEADROOM_TOKENS = 256;
3
+ const DEFAULT_COMPACTION_THRESHOLD_FRACTION = 0.8;
4
+ function requireSessionId(sessionId, operation) {
5
+ const normalized = typeof sessionId === "string" ? sessionId.trim() : "";
6
+ if (normalized.length > 0) {
7
+ return normalized;
8
+ }
9
+ throw new Error(`LibraVDB ${operation} requires a non-empty sessionId; refusing ambiguous request.`);
10
+ }
11
+ function normalizeCompactResult(response) {
12
+ const didCompact = response?.didCompact === true;
13
+ const details = {
14
+ clustersFormed: typeof response?.clustersFormed === "number" ? response.clustersFormed : undefined,
15
+ clustersDeclined: typeof response?.clustersDeclined === "number" ? response.clustersDeclined : undefined,
16
+ turnsRemoved: typeof response?.turnsRemoved === "number" ? response.turnsRemoved : undefined,
17
+ summaryMethod: typeof response?.summaryMethod === "string" && response.summaryMethod.length > 0
18
+ ? response.summaryMethod
19
+ : undefined,
20
+ meanConfidence: typeof response?.meanConfidence === "number" ? response.meanConfidence : undefined,
21
+ };
22
+ return {
23
+ ok: true,
24
+ compacted: didCompact,
25
+ ...(didCompact ? {} : { reason: "not_compacted" }),
26
+ result: {
27
+ tokensBefore: 0,
28
+ ...(details.summaryMethod ? { summary: details.summaryMethod } : {}),
29
+ details,
30
+ },
31
+ };
32
+ }
1
33
  function describeUnexpectedContent(value) {
2
34
  try {
3
35
  const serialized = JSON.stringify(value);
@@ -57,6 +89,108 @@ function normalizeKernelContent(content) {
57
89
  }
58
90
  return content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n");
59
91
  }
92
+ function approximateTokenCount(text) {
93
+ if (!text)
94
+ return 0;
95
+ return Math.ceil(text.length / APPROX_CHARS_PER_TOKEN);
96
+ }
97
+ function approximateMessageTokens(message) {
98
+ // Approximate per-message wrapper overhead so trimming is conservative.
99
+ return approximateTokenCount(message.content) + 8;
100
+ }
101
+ function approximateMessagesTokens(messages) {
102
+ return messages.reduce((sum, message) => sum + approximateMessageTokens(message), 0);
103
+ }
104
+ function normalizeTokenBudget(tokenBudget) {
105
+ if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
106
+ return undefined;
107
+ }
108
+ return Math.max(1, Math.floor(tokenBudget));
109
+ }
110
+ function resolveEffectiveAssembleBudget(tokenBudget) {
111
+ const normalized = normalizeTokenBudget(tokenBudget) ?? 1;
112
+ return Math.max(1, normalized - ASSEMBLE_BUDGET_HEADROOM_TOKENS);
113
+ }
114
+ function normalizeThresholdFraction(fraction) {
115
+ if (typeof fraction !== "number" || !Number.isFinite(fraction)) {
116
+ return DEFAULT_COMPACTION_THRESHOLD_FRACTION;
117
+ }
118
+ return Math.min(0.99, Math.max(0.05, fraction));
119
+ }
120
+ function resolveDynamicCompactThreshold(tokenBudget, compactThreshold, compactionThresholdFraction) {
121
+ if (typeof compactThreshold === "number" && Number.isFinite(compactThreshold) && compactThreshold > 0) {
122
+ return Math.max(1, Math.floor(compactThreshold));
123
+ }
124
+ const normalizedBudget = normalizeTokenBudget(tokenBudget);
125
+ if (normalizedBudget == null) {
126
+ return undefined;
127
+ }
128
+ const fraction = normalizeThresholdFraction(compactionThresholdFraction);
129
+ return Math.max(1, Math.floor(normalizedBudget * fraction));
130
+ }
131
+ function truncateContentToTokenBudget(content, tokenBudget) {
132
+ if (tokenBudget <= 0)
133
+ return "";
134
+ const maxChars = Math.max(1, tokenBudget * APPROX_CHARS_PER_TOKEN);
135
+ if (content.length <= maxChars)
136
+ return content;
137
+ // Keep the tail so recent tool output / latest answer content is preserved.
138
+ return content.slice(content.length - maxChars);
139
+ }
140
+ function trimMessagesToBudget(messages, tokenBudget) {
141
+ if (tokenBudget <= 0 || messages.length === 0) {
142
+ return [];
143
+ }
144
+ const kept = [];
145
+ let used = 0;
146
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
147
+ const candidate = messages[i];
148
+ const cost = approximateMessageTokens(candidate);
149
+ if (used + cost > tokenBudget) {
150
+ continue;
151
+ }
152
+ kept.push(candidate);
153
+ used += cost;
154
+ }
155
+ if (kept.length > 0) {
156
+ return kept.reverse();
157
+ }
158
+ const last = messages[messages.length - 1];
159
+ const contentBudget = Math.max(1, tokenBudget - 8);
160
+ const truncated = truncateContentToTokenBudget(last.content, contentBudget);
161
+ if (!truncated) {
162
+ return [];
163
+ }
164
+ return [{ ...last, content: truncated }];
165
+ }
166
+ function enforceTokenBudgetInvariant(result, tokenBudget) {
167
+ if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
168
+ return result;
169
+ }
170
+ const hardBudget = Math.max(1, Math.floor(tokenBudget));
171
+ const effectiveBudget = resolveEffectiveAssembleBudget(hardBudget);
172
+ const estimated = typeof result.estimatedTokens === "number" ? result.estimatedTokens : 0;
173
+ const approxFromMessages = approximateMessagesTokens(result.messages);
174
+ if (estimated <= effectiveBudget && approxFromMessages <= effectiveBudget) {
175
+ return result;
176
+ }
177
+ const trimmedMessages = trimMessagesToBudget(result.messages, effectiveBudget);
178
+ const trimmedEstimate = approximateMessagesTokens(trimmedMessages);
179
+ return {
180
+ ...result,
181
+ messages: trimmedMessages,
182
+ estimatedTokens: Math.min(effectiveBudget, trimmedEstimate),
183
+ };
184
+ }
185
+ function buildBudgetFallbackContext(messages, tokenBudget) {
186
+ const effectiveBudget = resolveEffectiveAssembleBudget(tokenBudget);
187
+ const fallbackMessages = trimMessagesToBudget(messages.map((message) => ({ ...message })), effectiveBudget);
188
+ return {
189
+ messages: fallbackMessages,
190
+ estimatedTokens: approximateMessagesTokens(fallbackMessages),
191
+ systemPromptAddition: "",
192
+ };
193
+ }
60
194
  export function normalizeKernelMessage(message) {
61
195
  return {
62
196
  role: message.role,
@@ -87,6 +221,38 @@ export function normalizeAssembleResult(result) {
87
221
  };
88
222
  }
89
223
  export function buildContextEngineFactory(runtime, cfg, recallCache, logger = console) {
224
+ const getDynamicCompactThreshold = (tokenBudget) => resolveDynamicCompactThreshold(tokenBudget, cfg.compactThreshold, cfg.compactionThresholdFraction);
225
+ const buildAssemblyConfig = (tokenBudget) => ({
226
+ useSessionRecallProjection: cfg.useSessionRecallProjection,
227
+ useSessionSummarySearchExperiment: cfg.useSessionSummarySearchExperiment,
228
+ tokenBudgetFraction: cfg.tokenBudgetFraction,
229
+ authoredHardBudgetFraction: cfg.authoredHardBudgetFraction,
230
+ authoredSoftBudgetFraction: cfg.authoredSoftBudgetFraction,
231
+ elevatedGuidanceBudgetFraction: cfg.elevatedGuidanceBudgetFraction,
232
+ topK: cfg.topK,
233
+ continuityMinTurns: cfg.continuityMinTurns,
234
+ continuityTailBudgetTokens: cfg.continuityTailBudgetTokens,
235
+ continuityPriorContextTokens: cfg.continuityPriorContextTokens,
236
+ compactThreshold: getDynamicCompactThreshold(tokenBudget),
237
+ compactSessionTokenBudget: cfg.compactSessionTokenBudget,
238
+ section7Theta1: cfg.section7Theta1,
239
+ section7Kappa: cfg.section7Kappa,
240
+ section7HopEta: cfg.section7HopEta,
241
+ section7HopThreshold: cfg.section7HopThreshold,
242
+ section7CoarseTopK: cfg.section7CoarseTopK,
243
+ section7SecondPassTopK: cfg.section7SecondPassTopK,
244
+ section7AuthorityRecencyLambda: cfg.section7AuthorityRecencyLambda,
245
+ section7AuthorityRecencyWeight: cfg.section7AuthorityRecencyWeight,
246
+ section7AuthorityFrequencyWeight: cfg.section7AuthorityFrequencyWeight,
247
+ section7AuthorityAuthoredWeight: cfg.section7AuthorityAuthoredWeight,
248
+ recoveryFloorScore: cfg.recoveryFloorScore,
249
+ recoveryMinTopK: cfg.recoveryMinTopK,
250
+ recoveryMinConfidenceMean: cfg.recoveryMinConfidenceMean,
251
+ recencyLambdaSession: cfg.recencyLambdaSession,
252
+ recencyLambdaUser: cfg.recencyLambdaUser,
253
+ recencyLambdaGlobal: cfg.recencyLambdaGlobal,
254
+ ingestionGateThreshold: cfg.ingestionGateThreshold,
255
+ });
90
256
  function buildCompactSessionRequest(args) {
91
257
  // OpenClaw core now requests budget-style compaction using tokenBudget,
92
258
  // but the current LibraVDB compact_session wire contract still expects
@@ -94,7 +260,7 @@ export function buildContextEngineFactory(runtime, cfg, recallCache, logger = co
94
260
  // timeout retries still compact toward the host's requested prompt budget.
95
261
  const targetSize = args.targetSize ?? args.tokenBudget;
96
262
  return {
97
- sessionId: args.sessionId,
263
+ sessionId: requireSessionId(args.sessionId, "compact"),
98
264
  force: args.force,
99
265
  ...(typeof targetSize === "number" ? { targetSize } : {}),
100
266
  ...(typeof cfg.continuityMinTurns === "number"
@@ -108,6 +274,24 @@ export function buildContextEngineFactory(runtime, cfg, recallCache, logger = co
108
274
  : {}),
109
275
  };
110
276
  }
277
+ async function runCompaction(args) {
278
+ const request = buildCompactSessionRequest(args);
279
+ const kernel = runtime.getKernel();
280
+ try {
281
+ if (kernel) {
282
+ return normalizeCompactResult(await kernel.compactSession(request));
283
+ }
284
+ const rpc = await runtime.getRpc();
285
+ return normalizeCompactResult(await rpc.call("compact_session", request));
286
+ }
287
+ catch (error) {
288
+ return {
289
+ ok: false,
290
+ compacted: false,
291
+ reason: error instanceof Error ? error.message : String(error),
292
+ };
293
+ }
294
+ }
111
295
  return {
112
296
  info: { id: "libravdb-memory", name: "LibraVDB Memory", ownsCompaction: true },
113
297
  ownsCompaction: true,
@@ -152,70 +336,60 @@ export function buildContextEngineFactory(runtime, cfg, recallCache, logger = co
152
336
  },
153
337
  async assemble(args) {
154
338
  const messages = normalizeKernelMessages(args.messages);
339
+ const currentContextTokens = approximateMessagesTokens(messages) + approximateTokenCount(args.prompt ?? "");
340
+ const dynamicCompactThreshold = getDynamicCompactThreshold(args.tokenBudget);
341
+ if (dynamicCompactThreshold != null &&
342
+ currentContextTokens >= dynamicCompactThreshold) {
343
+ const compactionResult = await runCompaction({
344
+ sessionId: args.sessionId,
345
+ tokenBudget: args.tokenBudget,
346
+ force: true,
347
+ });
348
+ if (!compactionResult.ok || !compactionResult.compacted) {
349
+ logger.warn?.(`LibraVDB predictive compaction blocked assemble path at ${currentContextTokens} tokens (threshold=${dynamicCompactThreshold}): ${compactionResult.reason ?? "compaction declined"}`);
350
+ return buildBudgetFallbackContext(messages, args.tokenBudget);
351
+ }
352
+ }
155
353
  const kernel = runtime.getKernel();
156
354
  if (kernel) {
157
- return normalizeAssembleResult(await kernel.assembleContext({
355
+ try {
356
+ return enforceTokenBudgetInvariant(normalizeAssembleResult(await kernel.assembleContext({
357
+ sessionId: args.sessionId,
358
+ sessionKey: args.sessionKey,
359
+ userId: args.userId,
360
+ queryText: args.prompt ?? "",
361
+ visibleMessages: messages,
362
+ tokenBudget: args.tokenBudget,
363
+ config: buildAssemblyConfig(args.tokenBudget),
364
+ emitDebug: true
365
+ })), args.tokenBudget);
366
+ }
367
+ catch (error) {
368
+ logger.warn?.(`LibraVDB assemble kernel failed, using budget-clamped fallback context: ${error instanceof Error ? error.message : String(error)}`);
369
+ return buildBudgetFallbackContext(messages, args.tokenBudget);
370
+ }
371
+ }
372
+ const rpc = await runtime.getRpc();
373
+ try {
374
+ const resp = await rpc.call("assemble_context_internal", {
158
375
  sessionId: args.sessionId,
159
376
  sessionKey: args.sessionKey,
160
377
  userId: args.userId,
161
- queryText: args.prompt ?? "",
162
- visibleMessages: messages,
378
+ messages,
163
379
  tokenBudget: args.tokenBudget,
164
- config: {},
165
- emitDebug: true
166
- }));
380
+ prompt: args.prompt,
381
+ emitDebug: true,
382
+ config: buildAssemblyConfig(args.tokenBudget),
383
+ });
384
+ return enforceTokenBudgetInvariant(normalizeAssembleResult(resp), args.tokenBudget);
385
+ }
386
+ catch (error) {
387
+ logger.warn?.(`LibraVDB assemble sidecar failed, using budget-clamped fallback context: ${error instanceof Error ? error.message : String(error)}`);
388
+ return buildBudgetFallbackContext(messages, args.tokenBudget);
167
389
  }
168
- const rpc = await runtime.getRpc();
169
- const resp = await rpc.call("assemble_context_internal", {
170
- sessionId: args.sessionId,
171
- sessionKey: args.sessionKey,
172
- userId: args.userId,
173
- messages,
174
- tokenBudget: args.tokenBudget,
175
- prompt: args.prompt,
176
- emitDebug: true,
177
- config: {
178
- useSessionRecallProjection: cfg.useSessionRecallProjection,
179
- useSessionSummarySearchExperiment: cfg.useSessionSummarySearchExperiment,
180
- tokenBudgetFraction: cfg.tokenBudgetFraction,
181
- authoredHardBudgetFraction: cfg.authoredHardBudgetFraction,
182
- authoredSoftBudgetFraction: cfg.authoredSoftBudgetFraction,
183
- elevatedGuidanceBudgetFraction: cfg.elevatedGuidanceBudgetFraction,
184
- topK: cfg.topK,
185
- continuityMinTurns: cfg.continuityMinTurns,
186
- continuityTailBudgetTokens: cfg.continuityTailBudgetTokens,
187
- continuityPriorContextTokens: cfg.continuityPriorContextTokens,
188
- compactThreshold: cfg.compactThreshold,
189
- compactSessionTokenBudget: cfg.compactSessionTokenBudget,
190
- section7Theta1: cfg.section7Theta1,
191
- section7Kappa: cfg.section7Kappa,
192
- section7HopEta: cfg.section7HopEta,
193
- section7HopThreshold: cfg.section7HopThreshold,
194
- section7CoarseTopK: cfg.section7CoarseTopK,
195
- section7SecondPassTopK: cfg.section7SecondPassTopK,
196
- section7AuthorityRecencyLambda: cfg.section7AuthorityRecencyLambda,
197
- section7AuthorityRecencyWeight: cfg.section7AuthorityRecencyWeight,
198
- section7AuthorityFrequencyWeight: cfg.section7AuthorityFrequencyWeight,
199
- section7AuthorityAuthoredWeight: cfg.section7AuthorityAuthoredWeight,
200
- recoveryFloorScore: cfg.recoveryFloorScore,
201
- recoveryMinTopK: cfg.recoveryMinTopK,
202
- recoveryMinConfidenceMean: cfg.recoveryMinConfidenceMean,
203
- recencyLambdaSession: cfg.recencyLambdaSession,
204
- recencyLambdaUser: cfg.recencyLambdaUser,
205
- recencyLambdaGlobal: cfg.recencyLambdaGlobal,
206
- ingestionGateThreshold: cfg.ingestionGateThreshold,
207
- },
208
- });
209
- return normalizeAssembleResult(resp);
210
390
  },
211
391
  async compact(args) {
212
- const request = buildCompactSessionRequest(args);
213
- const kernel = runtime.getKernel();
214
- if (kernel) {
215
- return await kernel.compactSession(request);
216
- }
217
- const rpc = await runtime.getRpc();
218
- return await rpc.call("compact_session", request);
392
+ return await runCompaction(args);
219
393
  },
220
394
  async afterTurn(args) {
221
395
  const messages = normalizeKernelMessages(args.messages);
package/dist/types.d.ts CHANGED
@@ -63,6 +63,7 @@ export interface PluginConfig {
63
63
  continuityTailBudgetTokens?: number;
64
64
  continuityPriorContextTokens?: number;
65
65
  compactThreshold?: number;
66
+ compactionThresholdFraction?: number;
66
67
  compactSessionTokenBudget?: number;
67
68
  section7CoarseTopK?: number;
68
69
  section7SecondPassTopK?: number;
@@ -2,7 +2,7 @@
2
2
  "id": "libravdb-memory",
3
3
  "name": "LibraVDB Memory",
4
4
  "description": "Persistent vector memory with three-tier hybrid scoring",
5
- "version": "1.4.11",
5
+ "version": "1.4.13",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
@@ -237,6 +237,11 @@
237
237
  "compactThreshold": {
238
238
  "type": "number"
239
239
  },
240
+ "compactionThresholdFraction": {
241
+ "type": "number",
242
+ "default": 0.8,
243
+ "description": "Dynamic predictive compaction trigger ratio against the active token budget. Used when compactThreshold is not explicitly set."
244
+ },
240
245
  "compactSessionTokenBudget": {
241
246
  "type": "number",
242
247
  "default": 2000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.4.11",
3
+ "version": "1.4.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",