@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.
- package/dist/context-engine.d.ts +10 -2
- package/dist/context-engine.js +190 -15
- package/dist/index.js +171 -15
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/context-engine.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
}
|
|
59
|
+
}>, options?: KernelContentNormalizationOptions): KernelCompatibleMessage[];
|
|
52
60
|
/**
|
|
53
61
|
* Normalizes a compact result into the OpenClaw-compatible assemble result format.
|
|
54
62
|
*/
|
package/dist/context-engine.js
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
26762
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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);
|
package/openclaw.plugin.json
CHANGED