@mingxy/cerebro 1.5.6 → 1.5.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/cerebro",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "Cerebro persistent memory plugin for OpenCode — auto-recall, auto-capture, 9 memory tools with clustering",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/hooks.ts CHANGED
@@ -18,25 +18,6 @@ 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
-
40
21
  function formatRelativeAge(isoDate: string): string {
41
22
  const diffMs = Date.now() - new Date(isoDate).getTime();
42
23
  const minutes = Math.floor(diffMs / 60_000);
@@ -152,8 +133,6 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
152
133
  const query_text = userMessages[userMessages.length - 1]?.content || firstMessages.get(input.sessionID) || "";
153
134
  const last_query_text = userMessages.length >= 2 ? userMessages[userMessages.length - 2].content : undefined;
154
135
 
155
- if (!query_text.trim()) return;
156
-
157
136
  const projectTags = containerTags.filter(t => t.startsWith("omem_project_"));
158
137
  const shouldRecallRes = await client.shouldRecall(query_text, last_query_text, input.sessionID, similarityThreshold, maxRecallResults, projectTags.length > 0 ? projectTags : undefined);
159
138
 
@@ -280,7 +259,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
280
259
  };
281
260
  }
282
261
 
283
- export function keywordDetectionHook(client: OmemClient, containerTags: string[], threshold: number, tui: any, ingestMode: "smart" | "raw" = "smart") {
262
+ export function keywordDetectionHook(_client: OmemClient, _containerTags: string[], threshold: number, _tui: any, _ingestMode: "smart" | "raw" = "smart") {
284
263
  return async (
285
264
  input: { sessionID: string; messageID?: string },
286
265
  output: { message: UserMessage; parts: Part[] },
@@ -309,38 +288,47 @@ export function keywordDetectionHook(client: OmemClient, containerTags: string[]
309
288
  });
310
289
 
311
290
  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.
312
293
  if (messages.length >= threshold) {
313
- try {
314
- const result = await client.ingestMessages(messages, {
315
- mode: ingestMode,
316
- tags: [...containerTags, "auto-capture"],
317
- sessionId: input.sessionID,
318
- });
319
- if (result === null) {
320
- showToast(tui, "🔴 Capture Failed", `Memory capture blocked · check API Key and spiritual connection`, "error");
321
- } else {
322
- showToast(tui, "🧠 Memory Sealed", `${messages.length} dialogues captured · entrusted to the heavens for refinement`, "success");
323
- const memoryIds = extractMemoryIds(result);
324
- if (memoryIds.length > 0) {
325
- const recordResult = await client.recordSessionRecall(
326
- input.sessionID,
327
- memoryIds,
328
- "auto",
329
- firstMessages.get(input.sessionID) || "",
330
- 0,
331
- 0,
332
- );
333
- if (recordResult) {
334
- showToast(tui, "📦 Capture Recorded", `${memoryIds.length} memory(s) saved to session history`, "success");
335
- } else {
336
- showToast(tui, "🔴 Capture Record Failed", `Failed to save capture record · check API connection`, "error");
337
- }
294
+ // Threshold reached — messages will be processed on next session.idle
295
+ }
296
+ };
297
+ }
298
+
299
+ export function compactingHook(client: OmemClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart") {
300
+ return async (
301
+ input: { sessionID?: string },
302
+ output: { context: string[]; prompt?: string },
303
+ ) => {
304
+ if (input.sessionID && sessionMessages.has(input.sessionID)) {
305
+ const messages = sessionMessages.get(input.sessionID)!;
306
+ if (messages.length > 0) {
307
+ try {
308
+ const result = await client.ingestMessages(messages, {
309
+ mode: ingestMode,
310
+ tags: [...containerTags, "auto-capture"],
311
+ sessionId: input.sessionID,
312
+ });
313
+ if (result === null) {
314
+ showToast(tui, "🔴 Archive Failed", "Session archive blocked · check spiritual realm status", "error");
315
+ } else {
316
+ showToast(tui, "📦 Session Archived", `${messages.length} residual dialogues archived · merged into the realm`, "success");
338
317
  }
339
- sessionMessages.delete(input.sessionID);
318
+ } catch {
319
+ showToast(tui, "🔴 Archive Failed", "Session archive blocked · spiritual pulse anomaly", "error");
340
320
  }
341
- } catch {
342
- showToast(tui, "🔴 Capture Failed", "Memory capture blocked · spiritual pulse anomaly", "error");
321
+ sessionMessages.delete(input.sessionID);
322
+ }
323
+ }
324
+
325
+ try {
326
+ const results = await client.searchMemories("*", 20, undefined, containerTags);
327
+ const block = buildContextBlock(results);
328
+ if (block) {
329
+ output.context.push(block);
343
330
  }
331
+ } catch {
344
332
  }
345
333
  };
346
334
  }
@@ -353,6 +341,7 @@ export function sessionIdleHook(
353
341
  tui: any,
354
342
  sdkClient: any,
355
343
  _ingestMode: "smart" | "raw" = "smart",
344
+ threshold: number = 0,
356
345
  getMainSessionId?: () => string | undefined,
357
346
  ) {
358
347
  let idleTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -404,15 +393,18 @@ export function sessionIdleHook(
404
393
 
405
394
  if (!hasNewMessages || conversationMessages.length === 0) return;
406
395
 
396
+ if (threshold > 1 && conversationMessages.length < threshold) {
397
+ // Log that we're waiting for more messages
398
+ return;
399
+ }
400
+
407
401
  try {
408
402
  await omemClient.sessionIngest(conversationMessages, sessionID);
409
- // Only mark as processed after successful ingest to prevent message loss on server errors
410
403
  for (const id of newMessageIds) {
411
404
  processedMessageIds.add(id);
412
405
  }
413
406
  showToast(tui, "🧠 Memory Sealed", `${conversationMessages.length} dialogues captured · entrusted to the heavens for refinement`, "success");
414
407
  } catch (err) {
415
- // Do NOT mark as processed — messages will be retried on next idle event
416
408
  showToast(tui, "🔴 Session Capture Failed", String(err).substring(0, 100), "error");
417
409
  }
418
410
  } catch (err) {
@@ -425,40 +417,3 @@ export function sessionIdleHook(
425
417
  }, 10000);
426
418
  };
427
419
  }
428
-
429
- export function compactingHook(client: OmemClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart") {
430
- return async (
431
- input: { sessionID?: string },
432
- output: { context: string[]; prompt?: string },
433
- ) => {
434
- if (input.sessionID && sessionMessages.has(input.sessionID)) {
435
- const messages = sessionMessages.get(input.sessionID)!;
436
- if (messages.length > 0) {
437
- try {
438
- const result = await client.ingestMessages(messages, {
439
- mode: ingestMode,
440
- tags: [...containerTags, "auto-capture"],
441
- sessionId: input.sessionID,
442
- });
443
- if (result === null) {
444
- showToast(tui, "🔴 Archive Failed", "Session archive blocked · check spiritual realm status", "error");
445
- } else {
446
- showToast(tui, "📦 Session Archived", `${messages.length} residual dialogues archived · merged into the realm`, "success");
447
- }
448
- } catch {
449
- showToast(tui, "🔴 Archive Failed", "Session archive blocked · spiritual pulse anomaly", "error");
450
- }
451
- sessionMessages.delete(input.sessionID);
452
- }
453
- }
454
-
455
- try {
456
- const results = await client.searchMemories("*", 20, undefined, containerTags);
457
- const block = buildContextBlock(results);
458
- if (block) {
459
- output.context.push(block);
460
- }
461
- } catch {
462
- }
463
- };
464
- }
package/src/index.ts CHANGED
@@ -96,7 +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, () => currentSessionId),
99
+ event: sessionIdleHook(omemClient, containerTags, tui, client, config.ingestMode, config.autoCaptureThreshold, () => currentSessionId),
100
100
  "shell.env": async (_input: any, output: any) => {
101
101
  if (directory) {
102
102
  output.env.OMEM_PROJECT_DIR = directory;