@mingxy/cerebro 1.10.12 → 1.11.2
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/cerebro.example.jsonc +1 -0
- package/dist/client.d.ts +7 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +38 -4
- package/dist/config.js.map +1 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +141 -47
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +4 -1
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +163 -0
- package/src/client.ts +9 -0
- package/src/hooks.ts +73 -22
package/src/hooks.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type OmemPluginConfig, resolveAgentPolicy } from "./config.js";
|
|
|
4
4
|
import { detectKeyword, KEYWORD_NUDGE } from "./keywords.js";
|
|
5
5
|
import { logDebug, logInfo, logError as logErr } from "./logger.js";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { stripPrivateContent } from "./privacy.js";
|
|
7
8
|
|
|
8
9
|
const BOUNDARY_SEARCH_RATIO = 0.6;
|
|
9
10
|
const MIN_ITEM_CONTENT_CHARS = 100;
|
|
@@ -135,7 +136,7 @@ const keywordDetectedSessions = new Set<string>();
|
|
|
135
136
|
const injectedMemoryIds = new Map<string, Set<string>>();
|
|
136
137
|
const firstMessages = new Map<string, string>();
|
|
137
138
|
const sessionMessages = new Map<string, Array<{ role: string; content: string }>>();
|
|
138
|
-
const profileInjectedSessions = new
|
|
139
|
+
const profileInjectedSessions = new Map<string, number>();
|
|
139
140
|
|
|
140
141
|
function formatRelativeAge(isoDate: string): string {
|
|
141
142
|
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
@@ -186,6 +187,17 @@ function categorize(results: SearchResult[]): Map<string, SearchResult[]> {
|
|
|
186
187
|
return groups;
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
function formatMemoryLine(r: SearchResult, maxContentLength: number): string {
|
|
191
|
+
const age = formatRelativeAge(r.memory.created_at);
|
|
192
|
+
const tags = r.memory.tags.length > 0 ? ` [${r.memory.tags.join(", ")}]` : "";
|
|
193
|
+
const idTag = ` [id:${r.memory.id}]`;
|
|
194
|
+
const relTag = r.memory.relations && r.memory.relations.length > 0
|
|
195
|
+
? ` [rel:${r.memory.relations.map((rel) => rel.target_id).join(",")}]`
|
|
196
|
+
: "";
|
|
197
|
+
const content = truncate(r.memory.content, maxContentLength);
|
|
198
|
+
return ` - (${age}${idTag}${relTag}${tags}) ${content}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
189
201
|
function buildContextBlock(results: SearchResult[], maxContentLength: number = 500): string {
|
|
190
202
|
if (results.length === 0) return "";
|
|
191
203
|
|
|
@@ -193,19 +205,14 @@ function buildContextBlock(results: SearchResult[], maxContentLength: number = 5
|
|
|
193
205
|
const sections: string[] = [];
|
|
194
206
|
|
|
195
207
|
for (const [label, items] of grouped) {
|
|
196
|
-
const lines = items.map((r) =>
|
|
197
|
-
const tags = r.memory.tags.length > 0 ? ` [${r.memory.tags.join(", ")}]` : "";
|
|
198
|
-
const age = formatRelativeAge(r.memory.created_at);
|
|
199
|
-
const content = truncate(r.memory.content, maxContentLength);
|
|
200
|
-
return ` - (${age}${tags}) ${content}`;
|
|
201
|
-
});
|
|
208
|
+
const lines = items.map((r) => formatMemoryLine(r, maxContentLength));
|
|
202
209
|
sections.push(`[${label}]\n${lines.join("\n")}`);
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
return [
|
|
206
213
|
"<cerebro-context>",
|
|
207
|
-
"
|
|
208
|
-
"
|
|
214
|
+
"You may use memory_get(<id>) to fetch the full content of any memory above if the summary seems incomplete but relevant.",
|
|
215
|
+
"Related memories listed in [rel:<id>,...] can also be fetched if they may add useful context.",
|
|
209
216
|
"",
|
|
210
217
|
...sections,
|
|
211
218
|
"</cerebro-context>",
|
|
@@ -224,9 +231,13 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
224
231
|
if (cs.key_memories.length > 0) {
|
|
225
232
|
sections.push("**核心要点:**");
|
|
226
233
|
for (const mem of cs.key_memories) {
|
|
227
|
-
const
|
|
234
|
+
const idTag = mem.id ? ` [id:${mem.id}]` : "";
|
|
235
|
+
const relTag = mem.relations && mem.relations.length > 0
|
|
236
|
+
? ` [rel:${mem.relations.map((rel) => rel.target_id).join(",")}]`
|
|
237
|
+
: "";
|
|
228
238
|
const importanceBar = mem.importance >= 0.7 ? "●" : mem.importance >= 0.4 ? "◐" : "○";
|
|
229
|
-
|
|
239
|
+
const content = truncate(mem.content, maxContentLength);
|
|
240
|
+
sections.push(`- ${importanceBar}${idTag}${relTag} ${content}`);
|
|
230
241
|
}
|
|
231
242
|
}
|
|
232
243
|
}
|
|
@@ -235,15 +246,19 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
235
246
|
if (clustered.standalone_memories.length > 0) {
|
|
236
247
|
sections.push("\n## 📌 补充信息");
|
|
237
248
|
for (const mem of clustered.standalone_memories) {
|
|
249
|
+
const idTag = mem.id ? ` [id:${mem.id}]` : "";
|
|
250
|
+
const relTag = mem.relations && mem.relations.length > 0
|
|
251
|
+
? ` [rel:${mem.relations.map((rel) => rel.target_id).join(",")}]`
|
|
252
|
+
: "";
|
|
238
253
|
const content = truncate(mem.content, maxContentLength);
|
|
239
|
-
sections.push(
|
|
254
|
+
sections.push(`-${idTag}${relTag} ${content}`);
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
|
|
243
258
|
return [
|
|
244
259
|
"<cerebro-context>",
|
|
245
|
-
"
|
|
246
|
-
"
|
|
260
|
+
"You may use memory_get(<id>) to fetch the full content of any memory above if the summary seems incomplete but relevant.",
|
|
261
|
+
"Related memories listed in [rel:<id>,...] can also be fetched if they may add useful context.",
|
|
247
262
|
"",
|
|
248
263
|
...sections,
|
|
249
264
|
"</cerebro-context>",
|
|
@@ -277,7 +292,20 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
277
292
|
const last_query_text = userMessages.length >= 2 ? userMessages[userMessages.length - 2].content : undefined;
|
|
278
293
|
|
|
279
294
|
const projectTags = containerTags.filter(t => t.startsWith("omem_project_"));
|
|
280
|
-
|
|
295
|
+
|
|
296
|
+
const conversationContext = userMessages.length >= 2
|
|
297
|
+
? userMessages.slice(-4, -1).map((m) => {
|
|
298
|
+
const stripped = stripPrivateContent(m.content);
|
|
299
|
+
return stripped.length > 200 ? stripped.slice(0, 200) : stripped;
|
|
300
|
+
})
|
|
301
|
+
: undefined;
|
|
302
|
+
|
|
303
|
+
const shouldRecallRes = await client.shouldRecall(
|
|
304
|
+
query_text, last_query_text, input.sessionID,
|
|
305
|
+
similarityThreshold, maxRecallResults,
|
|
306
|
+
projectTags.length > 0 ? projectTags : undefined,
|
|
307
|
+
conversationContext && conversationContext.length > 0 ? conversationContext : undefined,
|
|
308
|
+
);
|
|
281
309
|
|
|
282
310
|
if (!shouldRecallRes) {
|
|
283
311
|
showToast(tui, "🧠 Cerebro Service Unavailable", "Unable to reach memory API · check connection", "error", toastDelayMs);
|
|
@@ -289,24 +317,36 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
289
317
|
let profileInjected = false;
|
|
290
318
|
let profileCountText = "";
|
|
291
319
|
let profileBlock = "";
|
|
292
|
-
|
|
320
|
+
const lastInjected = profileInjectedSessions.get(input.sessionID);
|
|
321
|
+
const ttlExpired = !lastInjected || (Date.now() - lastInjected > 5 * 60 * 1000);
|
|
322
|
+
const isFirstInjection = !lastInjected;
|
|
323
|
+
if (profile && ttlExpired) {
|
|
293
324
|
profileBlock = [
|
|
294
325
|
"<cerebro-profile>",
|
|
295
326
|
JSON.stringify(profile),
|
|
296
327
|
"</cerebro-profile>",
|
|
297
328
|
].join("\n");
|
|
298
|
-
output.system.
|
|
329
|
+
const existingIdx = output.system.findIndex((s) => s.includes("<cerebro-profile>"));
|
|
330
|
+
if (existingIdx >= 0) {
|
|
331
|
+
output.system[existingIdx] = profileBlock;
|
|
332
|
+
} else {
|
|
333
|
+
output.system.push(profileBlock);
|
|
334
|
+
}
|
|
299
335
|
profileInjected = true;
|
|
300
|
-
profileInjectedSessions.
|
|
336
|
+
profileInjectedSessions.set(input.sessionID, Date.now());
|
|
301
337
|
const p = profile as any;
|
|
302
338
|
const dynamicCount = p?.dynamic_context?.length ?? 0;
|
|
303
339
|
const staticCount = p?.static_facts?.length ?? 0;
|
|
304
340
|
profileCountText = `Dynamic(${dynamicCount}) · Static(${staticCount})`;
|
|
305
|
-
|
|
341
|
+
if (isFirstInjection) {
|
|
342
|
+
logDebug("autoRecallHook profile injected (first)", { dynamicCount, staticCount });
|
|
343
|
+
} else {
|
|
344
|
+
logDebug("autoRecallHook profile refreshed (TTL)", { dynamicCount, staticCount });
|
|
345
|
+
}
|
|
306
346
|
}
|
|
307
347
|
|
|
308
348
|
if (!shouldRecallRes.should_recall) {
|
|
309
|
-
if (profileInjected) {
|
|
349
|
+
if (profileInjected && isFirstInjection) {
|
|
310
350
|
showToast(tui, "👨 Profile Injected", `${profileCountText} · no memory recall needed`, "success", toastDelayMs);
|
|
311
351
|
}
|
|
312
352
|
return;
|
|
@@ -319,7 +359,7 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
319
359
|
const newResults = results.filter((r) => !existingIds.has(r.memory.id));
|
|
320
360
|
logDebug("autoRecallHook dedup", { totalResults: results.length, existingCount: existingIds.size, newCount: newResults.length });
|
|
321
361
|
if (newResults.length === 0) {
|
|
322
|
-
if (profileInjected) {
|
|
362
|
+
if (profileInjected && isFirstInjection) {
|
|
323
363
|
showToast(tui, "👨 Profile Injected", `${profileCountText} · all memories already injected`, "success", toastDelayMs);
|
|
324
364
|
}
|
|
325
365
|
return;
|
|
@@ -495,13 +535,21 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
495
535
|
const policy = resolveAgentPolicy(effectiveAgentId, config);
|
|
496
536
|
if (policy !== "readwrite") {
|
|
497
537
|
logInfo("compactingHook blocked by policy", { agentId: effectiveAgentId, policy });
|
|
498
|
-
if (input.sessionID)
|
|
538
|
+
if (input.sessionID) {
|
|
539
|
+
sessionMessages.delete(input.sessionID);
|
|
540
|
+
profileInjectedSessions.delete(input.sessionID);
|
|
541
|
+
injectedMemoryIds.delete(input.sessionID);
|
|
542
|
+
firstMessages.delete(input.sessionID);
|
|
543
|
+
}
|
|
499
544
|
return;
|
|
500
545
|
}
|
|
501
546
|
|
|
502
547
|
if (input.sessionID && sessionMessages.has(input.sessionID)) {
|
|
503
548
|
if (isAutoStoreEnabled && !isAutoStoreEnabled(input.sessionID)) {
|
|
504
549
|
sessionMessages.delete(input.sessionID);
|
|
550
|
+
profileInjectedSessions.delete(input.sessionID);
|
|
551
|
+
injectedMemoryIds.delete(input.sessionID);
|
|
552
|
+
firstMessages.delete(input.sessionID);
|
|
505
553
|
} else {
|
|
506
554
|
const messages = sessionMessages.get(input.sessionID)!;
|
|
507
555
|
if (messages.length > 0) {
|
|
@@ -543,6 +591,9 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
543
591
|
showToast(tui, "🔴 Archive Failed", "Session archive blocked · spiritual pulse anomaly", "error");
|
|
544
592
|
}
|
|
545
593
|
sessionMessages.delete(input.sessionID);
|
|
594
|
+
profileInjectedSessions.delete(input.sessionID);
|
|
595
|
+
injectedMemoryIds.delete(input.sessionID);
|
|
596
|
+
firstMessages.delete(input.sessionID);
|
|
546
597
|
}
|
|
547
598
|
}
|
|
548
599
|
}
|