@mingxy/cerebro 1.4.1 → 1.5.0
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/package.json +1 -1
- package/src/client.ts +15 -0
- package/src/hooks.ts +103 -2
- package/src/index.ts +2 -1
- package/src/tools.ts +5 -0
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface MemoryDto {
|
|
|
84
84
|
source?: string;
|
|
85
85
|
tenant_id: string;
|
|
86
86
|
agent_id?: string;
|
|
87
|
+
importance: number;
|
|
87
88
|
created_at: string;
|
|
88
89
|
updated_at: string;
|
|
89
90
|
}
|
|
@@ -171,6 +172,7 @@ export class OmemClient {
|
|
|
171
172
|
scope?: string,
|
|
172
173
|
agentId?: string,
|
|
173
174
|
sessionId?: string,
|
|
175
|
+
visibility?: string,
|
|
174
176
|
): Promise<MemoryDto | null> {
|
|
175
177
|
const safeContent = sanitizeContent(content, this.getCfg("maxContentChars", 30000));
|
|
176
178
|
return this.post<MemoryDto>("/v1/memories", {
|
|
@@ -180,6 +182,7 @@ export class OmemClient {
|
|
|
180
182
|
scope,
|
|
181
183
|
agent_id: agentId,
|
|
182
184
|
session_id: sessionId,
|
|
185
|
+
visibility,
|
|
183
186
|
});
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -356,4 +359,16 @@ export class OmemClient {
|
|
|
356
359
|
);
|
|
357
360
|
return res?.recalls ?? [];
|
|
358
361
|
}
|
|
362
|
+
|
|
363
|
+
async sessionIngest(
|
|
364
|
+
summaries: Array<{ topic: string; content: string }>,
|
|
365
|
+
sessionId?: string,
|
|
366
|
+
agentId?: string,
|
|
367
|
+
): Promise<unknown> {
|
|
368
|
+
return this.post("/v1/memories/session-ingest", {
|
|
369
|
+
summaries,
|
|
370
|
+
session_id: sessionId,
|
|
371
|
+
agent_id: agentId,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
359
374
|
}
|
package/src/hooks.ts
CHANGED
|
@@ -102,13 +102,15 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
102
102
|
if (clustered.cluster_summaries.length > 0) {
|
|
103
103
|
sections.push("## 📋 主题簇(聚合记忆)");
|
|
104
104
|
for (const cs of clustered.cluster_summaries) {
|
|
105
|
-
|
|
105
|
+
const scoreIndicator = cs.relevance_score >= 0.8 ? "★★★" : cs.relevance_score >= 0.6 ? "★★" : "★";
|
|
106
|
+
sections.push(`\n### ${cs.title} (整合自${cs.member_count}条记忆) ${scoreIndicator}`);
|
|
106
107
|
sections.push(`> ${cs.summary}`);
|
|
107
108
|
if (cs.key_memories.length > 0) {
|
|
108
109
|
sections.push("**核心要点:**");
|
|
109
110
|
for (const mem of cs.key_memories) {
|
|
110
111
|
const content = truncate(mem.content, maxContentLength);
|
|
111
|
-
|
|
112
|
+
const importanceBar = mem.importance >= 0.7 ? "●" : mem.importance >= 0.4 ? "◐" : "○";
|
|
113
|
+
sections.push(`- ${importanceBar} ${content}`);
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
}
|
|
@@ -341,6 +343,105 @@ export function keywordDetectionHook(client: OmemClient, containerTags: string[]
|
|
|
341
343
|
};
|
|
342
344
|
}
|
|
343
345
|
|
|
346
|
+
const processedMessageIds = new Set<string>();
|
|
347
|
+
|
|
348
|
+
export function sessionIdleHook(
|
|
349
|
+
omemClient: OmemClient,
|
|
350
|
+
containerTags: string[],
|
|
351
|
+
tui: any,
|
|
352
|
+
sdkClient: any,
|
|
353
|
+
ingestMode: "smart" | "raw" = "smart",
|
|
354
|
+
) {
|
|
355
|
+
let idleTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
356
|
+
let isCapturing = false;
|
|
357
|
+
|
|
358
|
+
return async (input: { event: { type: string; properties?: any } }) => {
|
|
359
|
+
if (input.event.type !== "session.idle") return;
|
|
360
|
+
|
|
361
|
+
const sessionID = input.event.properties?.sessionID;
|
|
362
|
+
if (!sessionID) return;
|
|
363
|
+
|
|
364
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
365
|
+
|
|
366
|
+
idleTimeout = setTimeout(async () => {
|
|
367
|
+
if (isCapturing) return;
|
|
368
|
+
isCapturing = true;
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const response = await sdkClient.session.messages({ path: { id: sessionID } });
|
|
372
|
+
if (!response?.data) return;
|
|
373
|
+
|
|
374
|
+
const messages = response.data;
|
|
375
|
+
const conversationMessages: Array<{ role: string; content: string }> = [];
|
|
376
|
+
const dcpSummaries: Array<{ topic: string; content: string }> = [];
|
|
377
|
+
let hasNewMessages = false;
|
|
378
|
+
|
|
379
|
+
for (const msg of messages) {
|
|
380
|
+
const msgId = msg.info?.id;
|
|
381
|
+
if (!msgId || processedMessageIds.has(msgId)) continue;
|
|
382
|
+
|
|
383
|
+
const role = msg.info?.role;
|
|
384
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
385
|
+
|
|
386
|
+
const textParts = (msg.parts || [])
|
|
387
|
+
.filter((p: any) => p.type === "text" && p.text)
|
|
388
|
+
.map((p: any) => p.text);
|
|
389
|
+
const text = textParts.join("\n").trim();
|
|
390
|
+
if (!text) continue;
|
|
391
|
+
|
|
392
|
+
hasNewMessages = true;
|
|
393
|
+
processedMessageIds.add(msgId);
|
|
394
|
+
|
|
395
|
+
if (text.includes("[Compressed conversation section]")) {
|
|
396
|
+
const blockRegex = /\[Compressed conversation section\]\n([\s\S]*?)(?=<dcp-block|$)/g;
|
|
397
|
+
let match;
|
|
398
|
+
while ((match = blockRegex.exec(text)) !== null) {
|
|
399
|
+
const summary = match[1].trim();
|
|
400
|
+
if (summary) {
|
|
401
|
+
dcpSummaries.push({ topic: "session-compress", content: summary });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
conversationMessages.push({ role, content: text });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!hasNewMessages) return;
|
|
410
|
+
|
|
411
|
+
if (dcpSummaries.length > 0) {
|
|
412
|
+
try {
|
|
413
|
+
await omemClient.sessionIngest(dcpSummaries, sessionID);
|
|
414
|
+
showToast(tui, "🧠 DCP Summary Captured", `${dcpSummaries.length} compress summaries stored (zero-LLM)`, "success");
|
|
415
|
+
} catch (err) {
|
|
416
|
+
showToast(tui, "🔴 DCP Capture Failed", String(err).substring(0, 100), "error");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (conversationMessages.length > 0) {
|
|
421
|
+
try {
|
|
422
|
+
const result = await omemClient.ingestMessages(conversationMessages, {
|
|
423
|
+
mode: ingestMode,
|
|
424
|
+
tags: [...containerTags, "session-idle"],
|
|
425
|
+
sessionId: sessionID,
|
|
426
|
+
});
|
|
427
|
+
if (result) {
|
|
428
|
+
showToast(tui, "🧠 Session Captured", `${conversationMessages.length} messages captured via session.idle`, "success");
|
|
429
|
+
}
|
|
430
|
+
} catch (err) {
|
|
431
|
+
showToast(tui, "🔴 Session Capture Failed", String(err).substring(0, 100), "error");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} catch (err) {
|
|
435
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
436
|
+
showToast(tui, "🔴 Idle Capture Error", errMsg.substring(0, 100), "error");
|
|
437
|
+
} finally {
|
|
438
|
+
isCapturing = false;
|
|
439
|
+
idleTimeout = null;
|
|
440
|
+
}
|
|
441
|
+
}, 10000);
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
344
445
|
export function compactingHook(client: OmemClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart") {
|
|
345
446
|
return async (
|
|
346
447
|
input: { sessionID?: string },
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { OmemClient } from "./client.js";
|
|
6
|
-
import { autoRecallHook, compactingHook, keywordDetectionHook } from "./hooks.js";
|
|
6
|
+
import { autoRecallHook, compactingHook, keywordDetectionHook, sessionIdleHook } from "./hooks.js";
|
|
7
7
|
import { getUserTag, getProjectTag } from "./tags.js";
|
|
8
8
|
import { buildTools } from "./tools.js";
|
|
9
9
|
import { logInfo, logError } from "./logger.js";
|
|
@@ -96,6 +96,7 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
96
96
|
"chat.message": keywordDetectionHook(omemClient, containerTags, config.autoCaptureThreshold, tui, config.ingestMode),
|
|
97
97
|
"experimental.session.compacting": compactingHook(omemClient, containerTags, tui, config.ingestMode),
|
|
98
98
|
tool: buildTools(omemClient, containerTags, { agentId, getSessionId: () => currentSessionId }),
|
|
99
|
+
event: sessionIdleHook(omemClient, containerTags, tui, client, config.ingestMode),
|
|
99
100
|
"shell.env": async (_input: any, output: any) => {
|
|
100
101
|
if (directory) {
|
|
101
102
|
output.env.OMEM_PROJECT_DIR = directory;
|
package/src/tools.ts
CHANGED
|
@@ -45,6 +45,10 @@ export function buildTools(client: OmemClient, containerTags: string[], context:
|
|
|
45
45
|
.string()
|
|
46
46
|
.optional()
|
|
47
47
|
.describe("Memory scope: 'project' (default, only visible in this project) or 'global' (visible across all projects)"),
|
|
48
|
+
visibility: tool.schema
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Memory visibility: 'global' (default, visible to all agents) or 'private' (only visible to the storing agent). Use 'private' for sensitive data like credentials, personal info, or anything the user wouldn't want shared."),
|
|
48
52
|
},
|
|
49
53
|
async execute(args) {
|
|
50
54
|
const allTags = [...containerTags, ...(args.tags ?? [])];
|
|
@@ -55,6 +59,7 @@ export function buildTools(client: OmemClient, containerTags: string[], context:
|
|
|
55
59
|
args.scope ?? "project",
|
|
56
60
|
context.agentId,
|
|
57
61
|
context.getSessionId(),
|
|
62
|
+
args.visibility,
|
|
58
63
|
);
|
|
59
64
|
if (!result) return JSON.stringify({ ok: false, error: "The omem server may be unavailable." });
|
|
60
65
|
return JSON.stringify({ ok: true, id: result.id, tags: result.tags });
|