@mingxy/cerebro 1.5.8 → 1.5.10
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 +0 -12
- package/src/hooks.ts +51 -89
- package/src/index.ts +1 -2
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -359,16 +359,4 @@ export class OmemClient {
|
|
|
359
359
|
);
|
|
360
360
|
return res?.recalls ?? [];
|
|
361
361
|
}
|
|
362
|
-
|
|
363
|
-
async sessionIngest(
|
|
364
|
-
messages: Array<{ role: string; content: string }>,
|
|
365
|
-
sessionId?: string,
|
|
366
|
-
agentId?: string,
|
|
367
|
-
): Promise<unknown> {
|
|
368
|
-
return this.post("/v1/memories/session-ingest", {
|
|
369
|
-
messages,
|
|
370
|
-
session_id: sessionId,
|
|
371
|
-
agent_id: agentId,
|
|
372
|
-
}, 60000);
|
|
373
|
-
}
|
|
374
362
|
}
|
package/src/hooks.ts
CHANGED
|
@@ -18,6 +18,25 @@ const firstMessages = new Map<string, string>();
|
|
|
18
18
|
const sessionMessages = new Map<string, Array<{ role: string; content: string }>>();
|
|
19
19
|
const profileInjectedSessions = new Set<string>();
|
|
20
20
|
|
|
21
|
+
function extractMemoryIds(result: unknown): string[] {
|
|
22
|
+
if (!result) return [];
|
|
23
|
+
if (Array.isArray(result)) {
|
|
24
|
+
return (result as Array<{ id?: string }>).map((m) => m.id).filter(Boolean) as string[];
|
|
25
|
+
}
|
|
26
|
+
if (typeof result === "object" && result !== null) {
|
|
27
|
+
const r = result as Record<string, unknown>;
|
|
28
|
+
if (Array.isArray(r.memories)) {
|
|
29
|
+
return (r.memories as Array<{ id?: string }>).map((m) => m.id).filter(Boolean) as string[];
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(r.results)) {
|
|
32
|
+
return (r.results as Array<{ id?: string; memory?: { id?: string } }>)
|
|
33
|
+
.map((m) => m.id ?? m.memory?.id)
|
|
34
|
+
.filter(Boolean) as string[];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
function formatRelativeAge(isoDate: string): string {
|
|
22
41
|
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
23
42
|
const minutes = Math.floor(diffMs / 60_000);
|
|
@@ -259,7 +278,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
|
|
|
259
278
|
};
|
|
260
279
|
}
|
|
261
280
|
|
|
262
|
-
export function keywordDetectionHook(
|
|
281
|
+
export function keywordDetectionHook(client: OmemClient, containerTags: string[], threshold: number, tui: any, ingestMode: "smart" | "raw" = "smart") {
|
|
263
282
|
return async (
|
|
264
283
|
input: { sessionID: string; messageID?: string },
|
|
265
284
|
output: { message: UserMessage; parts: Part[] },
|
|
@@ -288,10 +307,38 @@ export function keywordDetectionHook(_client: OmemClient, _containerTags: string
|
|
|
288
307
|
});
|
|
289
308
|
|
|
290
309
|
const messages = sessionMessages.get(input.sessionID)!;
|
|
291
|
-
// Ingest is now handled by sessionIdleHook (session.idle → sessionIngest API).
|
|
292
|
-
// This hook only collects messages and detects keywords for recall.
|
|
293
310
|
if (messages.length >= threshold) {
|
|
294
|
-
|
|
311
|
+
try {
|
|
312
|
+
const result = await client.ingestMessages(messages, {
|
|
313
|
+
mode: ingestMode,
|
|
314
|
+
tags: [...containerTags, "auto-capture"],
|
|
315
|
+
sessionId: input.sessionID,
|
|
316
|
+
});
|
|
317
|
+
if (result === null) {
|
|
318
|
+
showToast(tui, "🔴 Capture Failed", `Memory capture blocked · check API Key and spiritual connection`, "error");
|
|
319
|
+
} else {
|
|
320
|
+
showToast(tui, "🧠 Memory Sealed", `${messages.length} dialogues captured · entrusted to the heavens for refinement`, "success");
|
|
321
|
+
const memoryIds = extractMemoryIds(result);
|
|
322
|
+
if (memoryIds.length > 0) {
|
|
323
|
+
const recordResult = await client.recordSessionRecall(
|
|
324
|
+
input.sessionID,
|
|
325
|
+
memoryIds,
|
|
326
|
+
"auto",
|
|
327
|
+
firstMessages.get(input.sessionID) || "",
|
|
328
|
+
0,
|
|
329
|
+
0,
|
|
330
|
+
);
|
|
331
|
+
if (recordResult) {
|
|
332
|
+
showToast(tui, "📦 Capture Recorded", `${memoryIds.length} memory(s) saved to session history`, "success");
|
|
333
|
+
} else {
|
|
334
|
+
showToast(tui, "🔴 Capture Record Failed", `Failed to save capture record · check API connection`, "error");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
sessionMessages.delete(input.sessionID);
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
showToast(tui, "🔴 Capture Failed", "Memory capture blocked · spiritual pulse anomaly", "error");
|
|
341
|
+
}
|
|
295
342
|
}
|
|
296
343
|
};
|
|
297
344
|
}
|
|
@@ -332,88 +379,3 @@ export function compactingHook(client: OmemClient, containerTags: string[], tui:
|
|
|
332
379
|
}
|
|
333
380
|
};
|
|
334
381
|
}
|
|
335
|
-
|
|
336
|
-
const processedMessageIds = new Set<string>();
|
|
337
|
-
|
|
338
|
-
export function sessionIdleHook(
|
|
339
|
-
omemClient: OmemClient,
|
|
340
|
-
_containerTags: string[],
|
|
341
|
-
tui: any,
|
|
342
|
-
sdkClient: any,
|
|
343
|
-
_ingestMode: "smart" | "raw" = "smart",
|
|
344
|
-
threshold: number = 0,
|
|
345
|
-
getMainSessionId?: () => string | undefined,
|
|
346
|
-
) {
|
|
347
|
-
let idleTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
348
|
-
let isCapturing = false;
|
|
349
|
-
|
|
350
|
-
return async (input: { event: { type: string; properties?: any } }) => {
|
|
351
|
-
if (input.event.type !== "session.idle") return;
|
|
352
|
-
|
|
353
|
-
const sessionID = input.event.properties?.sessionID;
|
|
354
|
-
if (!sessionID) return;
|
|
355
|
-
|
|
356
|
-
if (getMainSessionId) {
|
|
357
|
-
const mainId = getMainSessionId();
|
|
358
|
-
if (mainId && sessionID !== mainId) return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (idleTimeout) clearTimeout(idleTimeout);
|
|
362
|
-
|
|
363
|
-
idleTimeout = setTimeout(async () => {
|
|
364
|
-
if (isCapturing) return;
|
|
365
|
-
isCapturing = true;
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
const response = await sdkClient.session.messages({ path: { id: sessionID } });
|
|
369
|
-
if (!response?.data) return;
|
|
370
|
-
|
|
371
|
-
const messages = response.data;
|
|
372
|
-
const conversationMessages: Array<{ role: string; content: string }> = [];
|
|
373
|
-
const newMessageIds: string[] = [];
|
|
374
|
-
let hasNewMessages = false;
|
|
375
|
-
|
|
376
|
-
for (const msg of messages) {
|
|
377
|
-
const msgId = msg.info?.id;
|
|
378
|
-
if (!msgId || processedMessageIds.has(msgId)) continue;
|
|
379
|
-
|
|
380
|
-
const role = msg.info?.role;
|
|
381
|
-
if (role !== "user" && role !== "assistant") continue;
|
|
382
|
-
|
|
383
|
-
const textParts = (msg.parts || [])
|
|
384
|
-
.filter((p: any) => p.type === "text" && p.text)
|
|
385
|
-
.map((p: any) => p.text);
|
|
386
|
-
const text = textParts.join("\n").trim();
|
|
387
|
-
if (!text) continue;
|
|
388
|
-
|
|
389
|
-
hasNewMessages = true;
|
|
390
|
-
newMessageIds.push(msgId);
|
|
391
|
-
conversationMessages.push({ role, content: text });
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (!hasNewMessages || conversationMessages.length === 0) return;
|
|
395
|
-
|
|
396
|
-
if (threshold > 1 && conversationMessages.length < threshold) {
|
|
397
|
-
// Log that we're waiting for more messages
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
try {
|
|
402
|
-
await omemClient.sessionIngest(conversationMessages, sessionID);
|
|
403
|
-
for (const id of newMessageIds) {
|
|
404
|
-
processedMessageIds.add(id);
|
|
405
|
-
}
|
|
406
|
-
showToast(tui, "🧠 Memory Sealed", `${conversationMessages.length} dialogues captured · entrusted to the heavens for refinement`, "success");
|
|
407
|
-
} catch (err) {
|
|
408
|
-
showToast(tui, "🔴 Session Capture Failed", String(err).substring(0, 100), "error");
|
|
409
|
-
}
|
|
410
|
-
} catch (err) {
|
|
411
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
412
|
-
showToast(tui, "🔴 Idle Capture Error", errMsg.substring(0, 100), "error");
|
|
413
|
-
} finally {
|
|
414
|
-
isCapturing = false;
|
|
415
|
-
idleTimeout = null;
|
|
416
|
-
}
|
|
417
|
-
}, 10000);
|
|
418
|
-
};
|
|
419
|
-
}
|
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
|
|
6
|
+
import { autoRecallHook, compactingHook, keywordDetectionHook } 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,7 +96,6 @@ 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, config.autoCaptureThreshold, () => currentSessionId),
|
|
100
99
|
"shell.env": async (_input: any, output: any) => {
|
|
101
100
|
if (directory) {
|
|
102
101
|
output.env.OMEM_PROJECT_DIR = directory;
|