@xdarkicex/openclaw-memory-libravdb 1.6.27 → 1.6.28

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.
@@ -33,6 +33,9 @@ type OpenClawCompatibleCompactResult = {
33
33
  details?: unknown;
34
34
  };
35
35
  };
36
+ type KernelContentNormalizationOptions = {
37
+ retainOpenClawContext?: boolean;
38
+ };
36
39
  /**
37
40
  * Normalizes a single kernel message into the kernel-compatible format.
38
41
  */
@@ -40,15 +43,20 @@ export declare function normalizeKernelMessage(message: {
40
43
  role: string;
41
44
  content: unknown;
42
45
  id?: string;
43
- }): KernelCompatibleMessage;
46
+ [key: string]: unknown;
47
+ }, options?: KernelContentNormalizationOptions): KernelCompatibleMessage;
44
48
  /**
45
49
  * Normalizes an array of kernel messages.
50
+ *
51
+ * Non-user messages whose normalized content is empty or whitespace-only
52
+ * are dropped. This prevents assistant/system turns that consisted entirely
53
+ * of stripped metadata from persisting as empty records.
46
54
  */
47
55
  export declare function normalizeKernelMessages(messages: Array<{
48
56
  role: string;
49
57
  content: unknown;
50
58
  id?: string;
51
- }>): KernelCompatibleMessage[];
59
+ }>, options?: KernelContentNormalizationOptions): KernelCompatibleMessage[];
52
60
  /**
53
61
  * Normalizes a compact result into the OpenClaw-compatible assemble result format.
54
62
  */
@@ -14,6 +14,15 @@ const EXACT_RECALL_SEARCH_K = 32;
14
14
  const EXACT_RECALL_MAX_TOKENS = 4;
15
15
  const RESERVED_CURRENT_TURN_TOKENS = 150;
16
16
  const AFTER_TURN_INGEST_MAX_TOKENS = 2048;
17
+ const OPENCLAW_LEADING_TIMESTAMP_PREFIX_RE = /^\[[A-Za-z]{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2}[^\]]*\] */;
18
+ const OPENCLAW_METADATA_HEADERS = [
19
+ "Conversation info (untrusted metadata):",
20
+ "Sender (untrusted metadata):",
21
+ "Thread starter (untrusted, for context):",
22
+ "Reply target of current user message (untrusted, for context):",
23
+ "Forwarded message context (untrusted metadata):",
24
+ "Chat history since last reply (untrusted, for context):",
25
+ ];
17
26
  const COMMON_QUERY_WORDS = new Set([
18
27
  "what", "does", "mean", "remember", "recall", "about", "this", "that",
19
28
  "the", "and", "for", "with", "from", "your", "have", "been", "were",
@@ -96,14 +105,171 @@ function stringifyKernelBlock(block) {
96
105
  /**
97
106
  * Normalizes kernel content (string or block array) to a flat string.
98
107
  */
99
- function normalizeKernelContent(content) {
100
- if (typeof content === "string") {
101
- return content;
108
+ function normalizeKernelContent(content, options = {}) {
109
+ const text = typeof content === "string"
110
+ ? content
111
+ : Array.isArray(content)
112
+ ? content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n")
113
+ : "";
114
+ return stripOpenClawUntrustedMetadataEnvelope(text, {
115
+ retainContext: options.retainOpenClawContext === true,
116
+ });
117
+ }
118
+ function stripOpenClawUntrustedMetadataEnvelope(text, options = {}) {
119
+ let remaining = text
120
+ .replace(OPENCLAW_LEADING_TIMESTAMP_PREFIX_RE, "")
121
+ .replace(/\r\n/g, "\n");
122
+ // Capture any preamble that precedes the first metadata header.
123
+ const preambleEnd = findFirstHeaderPosition(remaining);
124
+ let preamble = "";
125
+ if (preambleEnd > 0) {
126
+ const newlineIndex = remaining.lastIndexOf("\n", preambleEnd);
127
+ preamble = newlineIndex >= 0 ? remaining.slice(0, newlineIndex + 1) : remaining.slice(0, preambleEnd);
128
+ remaining = remaining.slice(preamble.length);
102
129
  }
103
- if (!Array.isArray(content)) {
104
- return "";
130
+ const retainedContext = [];
131
+ let stripped = false;
132
+ while (true) {
133
+ const next = stripOneOpenClawMetadataBlock(remaining);
134
+ if (next.text === remaining) {
135
+ break;
136
+ }
137
+ stripped = true;
138
+ if (next.context.length > 0) {
139
+ retainedContext.push(...next.context);
140
+ }
141
+ remaining = next.text;
142
+ }
143
+ if (!stripped) {
144
+ return text;
145
+ }
146
+ const contextLine = options.retainContext === true
147
+ ? formatRetainedOpenClawContext(retainedContext)
148
+ : "";
149
+ const strippedText = remaining.trimStart();
150
+ const result = contextLine ? `${contextLine}\n${strippedText}` : strippedText;
151
+ return preamble ? `${preamble}${result}` : result;
152
+ }
153
+ function findFirstHeaderPosition(text) {
154
+ let pos = -1;
155
+ for (const header of OPENCLAW_METADATA_HEADERS) {
156
+ const p = text.indexOf(header);
157
+ if (p >= 0 && (pos < 0 || p < pos)) {
158
+ pos = p;
159
+ }
160
+ }
161
+ return pos;
162
+ }
163
+ function stripOneOpenClawMetadataBlock(text) {
164
+ const leadingWhitespaceLength = text.length - text.trimStart().length;
165
+ const offsetText = text.slice(leadingWhitespaceLength);
166
+ const header = OPENCLAW_METADATA_HEADERS.find((candidate) => offsetText.startsWith(candidate)) ?? null;
167
+ if (!header) {
168
+ return { text, context: [] };
169
+ }
170
+ const afterHeader = offsetText.slice(header.length);
171
+ const fenceStartMatch = afterHeader.match(/^\n```(?:json)?\n/i);
172
+ if (!fenceStartMatch) {
173
+ const afterHeaderLines = afterHeader.replace(/^\n?/, "").split("\n");
174
+ const firstBlankIndex = afterHeaderLines.findIndex((line) => line.trim() === "");
175
+ if (firstBlankIndex < 0) {
176
+ // No fence and no blank line — cannot positively identify envelope shape.
177
+ // Return original text unchanged to avoid silently erasing content.
178
+ return { text, context: [] };
179
+ }
180
+ return { text: afterHeaderLines.slice(firstBlankIndex + 1).join("\n"), context: [] };
181
+ }
182
+ const bodyStart = header.length + fenceStartMatch[0].length;
183
+ const fenceEnd = offsetText.indexOf("\n```", bodyStart);
184
+ if (fenceEnd < 0) {
185
+ // Unclosed fence — cannot positively identify envelope shape.
186
+ return { text, context: [] };
187
+ }
188
+ const jsonText = offsetText.slice(bodyStart, fenceEnd);
189
+ const afterFence = fenceEnd + "\n```".length;
190
+ const trailingNewlineLength = offsetText.slice(afterFence).startsWith("\n") ? 1 : 0;
191
+ return {
192
+ text: offsetText.slice(afterFence + trailingNewlineLength),
193
+ context: summarizeOpenClawMetadataBlock(header, jsonText),
194
+ };
195
+ }
196
+ function summarizeOpenClawMetadataBlock(header, jsonText) {
197
+ const parsed = parseJsonRecord(jsonText);
198
+ if (!parsed) {
199
+ return [];
200
+ }
201
+ if (header === "Conversation info (untrusted metadata):") {
202
+ const hasIMessageContext = firstString(parsed.chat_guid, parsed.chatGuid, parsed.chat_identifier, parsed.chatIdentifier, parsed.chat_name, parsed.chatName, parsed.service) != null;
203
+ return [
204
+ labelValue("channel", firstString(parsed.group_channel, parsed.channel, parsed.group_subject)),
205
+ labelValue("channel_id", firstString(parsed.chat_id, parsed.channel_id)),
206
+ labelValue("account_id", firstString(parsed.account_id, parsed.accountId)),
207
+ labelValue("provider", firstString(parsed.provider, parsed.surface)),
208
+ labelValue("chat_id", hasIMessageContext ? firstString(parsed.chat_id, parsed.chatId) : undefined),
209
+ labelValue("chat_guid", firstString(parsed.chat_guid, parsed.chatGuid)),
210
+ labelValue("chat_identifier", firstString(parsed.chat_identifier, parsed.chatIdentifier)),
211
+ labelValue("chat_name", firstString(parsed.chat_name, parsed.chatName)),
212
+ labelValue("is_group", firstString(parsed.is_group, parsed.isGroup, parsed.is_group_chat)),
213
+ labelValue("chat_type", firstString(parsed.chat_type, parsed.chatType)),
214
+ labelValue("service", firstString(parsed.service)),
215
+ labelValue("server_id", firstString(parsed.group_space, parsed.guild_id, parsed.server_id)),
216
+ labelValue("sender_id", firstString(parsed.sender_id, parsed.user_id)),
217
+ labelValue("sender", firstString(parsed.sender)),
218
+ labelValue("emoji_id", firstString(parsed.emoji_id, parsed.server_emoji_id, parsed.guild_emoji_id)),
219
+ labelValue("emoji", firstString(parsed.emoji_name, parsed.emoji)),
220
+ ].filter(isNonEmptyString);
221
+ }
222
+ if (header === "Sender (untrusted metadata):") {
223
+ return [
224
+ labelValue("username", firstString(parsed.username, parsed.tag, parsed.name, parsed.label)),
225
+ labelValue("user_id", firstString(parsed.id, parsed.user_id, parsed.sender_id)),
226
+ labelValue("sender", firstString(parsed.sender, parsed.e164)),
227
+ ].filter(isNonEmptyString);
228
+ }
229
+ return [];
230
+ }
231
+ function parseJsonRecord(jsonText) {
232
+ try {
233
+ const parsed = JSON.parse(jsonText);
234
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
235
+ ? parsed
236
+ : null;
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ function labelValue(label, value) {
243
+ return value ? `${label}=${sanitizeOpenClawContextValue(value)}` : "";
244
+ }
245
+ function firstString(...values) {
246
+ for (const value of values) {
247
+ if (typeof value === "string" && value.trim().length > 0) {
248
+ return value.trim();
249
+ }
250
+ if (typeof value === "number" && Number.isFinite(value)) {
251
+ return String(value);
252
+ }
253
+ if (typeof value === "boolean") {
254
+ return String(value);
255
+ }
105
256
  }
106
- return content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n");
257
+ return undefined;
258
+ }
259
+ function sanitizeOpenClawContextValue(value) {
260
+ // 120 chars is a conservative bound for a single routing field value
261
+ // (channel name, server id, etc.). Any field exceeding this is likely
262
+ // malformed or adversarial input, not useful routing metadata.
263
+ return value.replace(/[\r\n;]+/g, " ").trim().slice(0, 120);
264
+ }
265
+ function formatRetainedOpenClawContext(values) {
266
+ const uniqueValues = [...new Set(values.filter(isNonEmptyString))];
267
+ return uniqueValues.length > 0
268
+ ? `[OpenClaw context: ${uniqueValues.join("; ")}]`
269
+ : "";
270
+ }
271
+ function isNonEmptyString(value) {
272
+ return value.trim().length > 0;
107
273
  }
108
274
  /**
109
275
  * Approximates token count for a text string.
@@ -375,18 +541,24 @@ function resolveAfterTurnPredictiveCompactionTokenCount(args) {
375
541
  /**
376
542
  * Normalizes a single kernel message into the kernel-compatible format.
377
543
  */
378
- export function normalizeKernelMessage(message) {
544
+ export function normalizeKernelMessage(message, options = {}) {
379
545
  return {
380
546
  role: message.role,
381
- content: normalizeKernelContent(message.content),
382
- id: message.id || randomUUID(),
547
+ content: normalizeKernelContent(message.content, options),
548
+ id: typeof message.id === "string" ? message.id : randomUUID(),
383
549
  };
384
550
  }
385
551
  /**
386
552
  * Normalizes an array of kernel messages.
553
+ *
554
+ * Non-user messages whose normalized content is empty or whitespace-only
555
+ * are dropped. This prevents assistant/system turns that consisted entirely
556
+ * of stripped metadata from persisting as empty records.
387
557
  */
388
- export function normalizeKernelMessages(messages) {
389
- return messages.map((message) => normalizeKernelMessage(message));
558
+ export function normalizeKernelMessages(messages, options = {}) {
559
+ return messages
560
+ .map((message) => normalizeKernelMessage(message, options))
561
+ .filter((message) => message.role === "user" || message.content.trim().length > 0);
390
562
  }
391
563
  /**
392
564
  * Extracts tokens for exact recall matching from text.
@@ -1019,6 +1191,9 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1019
1191
  sessionKey: args.sessionKey,
1020
1192
  });
1021
1193
  const messages = normalizeKernelMessages(args.messages);
1194
+ const strippedPrompt = args.prompt
1195
+ ? normalizeKernelContent(args.prompt, { retainOpenClawContext: false })
1196
+ : "";
1022
1197
  const lastUserMessage = findLastReplaySafeUserMessage(messages);
1023
1198
  const reservedCurrentTurnTokens = lastUserMessage
1024
1199
  ? approximateMessageTokens(lastUserMessage)
@@ -1026,7 +1201,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1026
1201
  const currentContextTokens = resolvePredictiveCompactionTokenCount({
1027
1202
  currentTokenCount: args.currentTokenCount,
1028
1203
  messages,
1029
- prompt: args.prompt,
1204
+ prompt: strippedPrompt,
1030
1205
  });
1031
1206
  const dynamicCompactThreshold = getDynamicCompactThreshold(args.tokenBudget);
1032
1207
  const predictiveTargetSize = resolvePredictiveCompactionTarget({
@@ -1073,7 +1248,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1073
1248
  sessionId,
1074
1249
  sessionKey: args.sessionKey,
1075
1250
  userId,
1076
- prompt: args.prompt ?? "",
1251
+ prompt: strippedPrompt,
1077
1252
  messages,
1078
1253
  tokenBudget: args.tokenBudget,
1079
1254
  config: buildAssemblyConfig(args.tokenBudget),
@@ -1081,7 +1256,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1081
1256
  });
1082
1257
  const assembled = normalizeAssembleResult(resp, args.messages);
1083
1258
  let enforced = enforceTokenBudgetInvariant(await augmentWithExactRecall(assembled, {
1084
- queryText: args.prompt ?? messages[messages.length - 1]?.content ?? "",
1259
+ queryText: strippedPrompt || (messages[messages.length - 1]?.content ?? ""),
1085
1260
  userId,
1086
1261
  sessionId,
1087
1262
  tokenBudget: args.tokenBudget,
@@ -1163,7 +1338,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1163
1338
  // Load manifest and normalize messages in parallel
1164
1339
  const manifest = manifestStore.load(sessionId, logger);
1165
1340
  const afterTurnMessages = selectAfterTurnMessages(args.messages, args.prePromptMessageCount, logger);
1166
- const messages = normalizeKernelMessages(afterTurnMessages);
1341
+ const messages = normalizeKernelMessages(afterTurnMessages, { retainOpenClawContext: true });
1167
1342
  // Find overlap: messages already in our manifest
1168
1343
  const overlapIndex = manifestStore.findOverlapIndex(manifest, messages);
1169
1344
  const newMessages = messages.slice(overlapIndex);
package/dist/index.js CHANGED
@@ -26667,6 +26667,15 @@ var EXACT_RECALL_SEARCH_K = 32;
26667
26667
  var EXACT_RECALL_MAX_TOKENS = 4;
26668
26668
  var RESERVED_CURRENT_TURN_TOKENS = 150;
26669
26669
  var AFTER_TURN_INGEST_MAX_TOKENS = 2048;
26670
+ var OPENCLAW_LEADING_TIMESTAMP_PREFIX_RE = /^\[[A-Za-z]{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2}[^\]]*\] */;
26671
+ var OPENCLAW_METADATA_HEADERS = [
26672
+ "Conversation info (untrusted metadata):",
26673
+ "Sender (untrusted metadata):",
26674
+ "Thread starter (untrusted, for context):",
26675
+ "Reply target of current user message (untrusted, for context):",
26676
+ "Forwarded message context (untrusted metadata):",
26677
+ "Chat history since last reply (untrusted, for context):"
26678
+ ];
26670
26679
  var COMMON_QUERY_WORDS = /* @__PURE__ */ new Set([
26671
26680
  "what",
26672
26681
  "does",
@@ -26757,14 +26766,160 @@ function stringifyKernelBlock(block) {
26757
26766
  return typeof record.text === "string" ? record.text : "";
26758
26767
  }
26759
26768
  }
26760
- function normalizeKernelContent(content) {
26761
- if (typeof content === "string") {
26762
- return content;
26769
+ function normalizeKernelContent(content, options = {}) {
26770
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n") : "";
26771
+ return stripOpenClawUntrustedMetadataEnvelope(text, {
26772
+ retainContext: options.retainOpenClawContext === true
26773
+ });
26774
+ }
26775
+ function stripOpenClawUntrustedMetadataEnvelope(text, options = {}) {
26776
+ let remaining = text.replace(OPENCLAW_LEADING_TIMESTAMP_PREFIX_RE, "").replace(/\r\n/g, "\n");
26777
+ const preambleEnd = findFirstHeaderPosition(remaining);
26778
+ let preamble = "";
26779
+ if (preambleEnd > 0) {
26780
+ const newlineIndex = remaining.lastIndexOf("\n", preambleEnd);
26781
+ preamble = newlineIndex >= 0 ? remaining.slice(0, newlineIndex + 1) : remaining.slice(0, preambleEnd);
26782
+ remaining = remaining.slice(preamble.length);
26783
+ }
26784
+ const retainedContext = [];
26785
+ let stripped = false;
26786
+ while (true) {
26787
+ const next = stripOneOpenClawMetadataBlock(remaining);
26788
+ if (next.text === remaining) {
26789
+ break;
26790
+ }
26791
+ stripped = true;
26792
+ if (next.context.length > 0) {
26793
+ retainedContext.push(...next.context);
26794
+ }
26795
+ remaining = next.text;
26763
26796
  }
26764
- if (!Array.isArray(content)) {
26765
- return "";
26797
+ if (!stripped) {
26798
+ return text;
26799
+ }
26800
+ const contextLine = options.retainContext === true ? formatRetainedOpenClawContext(retainedContext) : "";
26801
+ const strippedText = remaining.trimStart();
26802
+ const result = contextLine ? `${contextLine}
26803
+ ${strippedText}` : strippedText;
26804
+ return preamble ? `${preamble}${result}` : result;
26805
+ }
26806
+ function findFirstHeaderPosition(text) {
26807
+ let pos = -1;
26808
+ for (const header of OPENCLAW_METADATA_HEADERS) {
26809
+ const p = text.indexOf(header);
26810
+ if (p >= 0 && (pos < 0 || p < pos)) {
26811
+ pos = p;
26812
+ }
26813
+ }
26814
+ return pos;
26815
+ }
26816
+ function stripOneOpenClawMetadataBlock(text) {
26817
+ const leadingWhitespaceLength = text.length - text.trimStart().length;
26818
+ const offsetText = text.slice(leadingWhitespaceLength);
26819
+ const header = OPENCLAW_METADATA_HEADERS.find((candidate) => offsetText.startsWith(candidate)) ?? null;
26820
+ if (!header) {
26821
+ return { text, context: [] };
26822
+ }
26823
+ const afterHeader = offsetText.slice(header.length);
26824
+ const fenceStartMatch = afterHeader.match(/^\n```(?:json)?\n/i);
26825
+ if (!fenceStartMatch) {
26826
+ const afterHeaderLines = afterHeader.replace(/^\n?/, "").split("\n");
26827
+ const firstBlankIndex = afterHeaderLines.findIndex((line) => line.trim() === "");
26828
+ if (firstBlankIndex < 0) {
26829
+ return { text, context: [] };
26830
+ }
26831
+ return { text: afterHeaderLines.slice(firstBlankIndex + 1).join("\n"), context: [] };
26832
+ }
26833
+ const bodyStart = header.length + fenceStartMatch[0].length;
26834
+ const fenceEnd = offsetText.indexOf("\n```", bodyStart);
26835
+ if (fenceEnd < 0) {
26836
+ return { text, context: [] };
26837
+ }
26838
+ const jsonText = offsetText.slice(bodyStart, fenceEnd);
26839
+ const afterFence = fenceEnd + "\n```".length;
26840
+ const trailingNewlineLength = offsetText.slice(afterFence).startsWith("\n") ? 1 : 0;
26841
+ return {
26842
+ text: offsetText.slice(afterFence + trailingNewlineLength),
26843
+ context: summarizeOpenClawMetadataBlock(header, jsonText)
26844
+ };
26845
+ }
26846
+ function summarizeOpenClawMetadataBlock(header, jsonText) {
26847
+ const parsed = parseJsonRecord(jsonText);
26848
+ if (!parsed) {
26849
+ return [];
26850
+ }
26851
+ if (header === "Conversation info (untrusted metadata):") {
26852
+ const hasIMessageContext = firstString2(
26853
+ parsed.chat_guid,
26854
+ parsed.chatGuid,
26855
+ parsed.chat_identifier,
26856
+ parsed.chatIdentifier,
26857
+ parsed.chat_name,
26858
+ parsed.chatName,
26859
+ parsed.service
26860
+ ) != null;
26861
+ return [
26862
+ labelValue("channel", firstString2(parsed.group_channel, parsed.channel, parsed.group_subject)),
26863
+ labelValue("channel_id", firstString2(parsed.chat_id, parsed.channel_id)),
26864
+ labelValue("account_id", firstString2(parsed.account_id, parsed.accountId)),
26865
+ labelValue("provider", firstString2(parsed.provider, parsed.surface)),
26866
+ labelValue("chat_id", hasIMessageContext ? firstString2(parsed.chat_id, parsed.chatId) : void 0),
26867
+ labelValue("chat_guid", firstString2(parsed.chat_guid, parsed.chatGuid)),
26868
+ labelValue("chat_identifier", firstString2(parsed.chat_identifier, parsed.chatIdentifier)),
26869
+ labelValue("chat_name", firstString2(parsed.chat_name, parsed.chatName)),
26870
+ labelValue("is_group", firstString2(parsed.is_group, parsed.isGroup, parsed.is_group_chat)),
26871
+ labelValue("chat_type", firstString2(parsed.chat_type, parsed.chatType)),
26872
+ labelValue("service", firstString2(parsed.service)),
26873
+ labelValue("server_id", firstString2(parsed.group_space, parsed.guild_id, parsed.server_id)),
26874
+ labelValue("sender_id", firstString2(parsed.sender_id, parsed.user_id)),
26875
+ labelValue("sender", firstString2(parsed.sender)),
26876
+ labelValue("emoji_id", firstString2(parsed.emoji_id, parsed.server_emoji_id, parsed.guild_emoji_id)),
26877
+ labelValue("emoji", firstString2(parsed.emoji_name, parsed.emoji))
26878
+ ].filter(isNonEmptyString);
26879
+ }
26880
+ if (header === "Sender (untrusted metadata):") {
26881
+ return [
26882
+ labelValue("username", firstString2(parsed.username, parsed.tag, parsed.name, parsed.label)),
26883
+ labelValue("user_id", firstString2(parsed.id, parsed.user_id, parsed.sender_id)),
26884
+ labelValue("sender", firstString2(parsed.sender, parsed.e164))
26885
+ ].filter(isNonEmptyString);
26886
+ }
26887
+ return [];
26888
+ }
26889
+ function parseJsonRecord(jsonText) {
26890
+ try {
26891
+ const parsed = JSON.parse(jsonText);
26892
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
26893
+ } catch {
26894
+ return null;
26895
+ }
26896
+ }
26897
+ function labelValue(label, value) {
26898
+ return value ? `${label}=${sanitizeOpenClawContextValue(value)}` : "";
26899
+ }
26900
+ function firstString2(...values) {
26901
+ for (const value of values) {
26902
+ if (typeof value === "string" && value.trim().length > 0) {
26903
+ return value.trim();
26904
+ }
26905
+ if (typeof value === "number" && Number.isFinite(value)) {
26906
+ return String(value);
26907
+ }
26908
+ if (typeof value === "boolean") {
26909
+ return String(value);
26910
+ }
26766
26911
  }
26767
- return content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n");
26912
+ return void 0;
26913
+ }
26914
+ function sanitizeOpenClawContextValue(value) {
26915
+ return value.replace(/[\r\n;]+/g, " ").trim().slice(0, 120);
26916
+ }
26917
+ function formatRetainedOpenClawContext(values) {
26918
+ const uniqueValues = [...new Set(values.filter(isNonEmptyString))];
26919
+ return uniqueValues.length > 0 ? `[OpenClaw context: ${uniqueValues.join("; ")}]` : "";
26920
+ }
26921
+ function isNonEmptyString(value) {
26922
+ return value.trim().length > 0;
26768
26923
  }
26769
26924
  function approximateTokenCount(text) {
26770
26925
  if (typeof text === "string") {
@@ -26979,15 +27134,15 @@ function resolveAfterTurnPredictiveCompactionTokenCount(args) {
26979
27134
  }
26980
27135
  return Math.max(currentTokenCount, forwardedMessageTokens);
26981
27136
  }
26982
- function normalizeKernelMessage(message) {
27137
+ function normalizeKernelMessage(message, options = {}) {
26983
27138
  return {
26984
27139
  role: message.role,
26985
- content: normalizeKernelContent(message.content),
26986
- id: message.id || randomUUID()
27140
+ content: normalizeKernelContent(message.content, options),
27141
+ id: typeof message.id === "string" ? message.id : randomUUID()
26987
27142
  };
26988
27143
  }
26989
- function normalizeKernelMessages(messages) {
26990
- return messages.map((message) => normalizeKernelMessage(message));
27144
+ function normalizeKernelMessages(messages, options = {}) {
27145
+ return messages.map((message) => normalizeKernelMessage(message, options)).filter((message) => message.role === "user" || message.content.trim().length > 0);
26991
27146
  }
26992
27147
  function extractExactRecallTokens(text) {
26993
27148
  const tokens = /* @__PURE__ */ new Set();
@@ -27533,12 +27688,13 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
27533
27688
  sessionKey: args.sessionKey
27534
27689
  });
27535
27690
  const messages = normalizeKernelMessages(args.messages);
27691
+ const strippedPrompt = args.prompt ? normalizeKernelContent(args.prompt, { retainOpenClawContext: false }) : "";
27536
27692
  const lastUserMessage = findLastReplaySafeUserMessage(messages);
27537
27693
  const reservedCurrentTurnTokens = lastUserMessage ? approximateMessageTokens(lastUserMessage) : RESERVED_CURRENT_TURN_TOKENS;
27538
27694
  const currentContextTokens = resolvePredictiveCompactionTokenCount({
27539
27695
  currentTokenCount: args.currentTokenCount,
27540
27696
  messages,
27541
- prompt: args.prompt
27697
+ prompt: strippedPrompt
27542
27698
  });
27543
27699
  const dynamicCompactThreshold = getDynamicCompactThreshold(args.tokenBudget);
27544
27700
  const predictiveTargetSize = resolvePredictiveCompactionTarget({
@@ -27586,7 +27742,7 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
27586
27742
  sessionId,
27587
27743
  sessionKey: args.sessionKey,
27588
27744
  userId,
27589
- prompt: args.prompt ?? "",
27745
+ prompt: strippedPrompt,
27590
27746
  messages,
27591
27747
  tokenBudget: args.tokenBudget,
27592
27748
  config: buildAssemblyConfig(args.tokenBudget),
@@ -27595,7 +27751,7 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
27595
27751
  const assembled = normalizeAssembleResult(resp, args.messages);
27596
27752
  let enforced = enforceTokenBudgetInvariant(
27597
27753
  await augmentWithExactRecall(assembled, {
27598
- queryText: args.prompt ?? messages[messages.length - 1]?.content ?? "",
27754
+ queryText: strippedPrompt || (messages[messages.length - 1]?.content ?? ""),
27599
27755
  userId,
27600
27756
  sessionId,
27601
27757
  tokenBudget: args.tokenBudget,
@@ -27680,7 +27836,7 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
27680
27836
  });
27681
27837
  const manifest = manifestStore.load(sessionId, logger);
27682
27838
  const afterTurnMessages = selectAfterTurnMessages(args.messages, args.prePromptMessageCount, logger);
27683
- const messages = normalizeKernelMessages(afterTurnMessages);
27839
+ const messages = normalizeKernelMessages(afterTurnMessages, { retainOpenClawContext: true });
27684
27840
  const overlapIndex = manifestStore.findOverlapIndex(manifest, messages);
27685
27841
  const newMessages = messages.slice(overlapIndex);
27686
27842
  const ingestMessages = boundAfterTurnMessagesForIngest(newMessages, logger, sessionId);
@@ -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.6.27",
5
+ "version": "1.6.28",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.6.27",
3
+ "version": "1.6.28",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",