@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/cerebro",
3
- "version": "1.5.8",
3
+ "version": "1.5.10",
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/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(_client: OmemClient, _containerTags: string[], threshold: number, _tui: any, _ingestMode: "smart" | "raw" = "smart") {
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
- // Threshold reached — messages will be processed on next session.idle
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, sessionIdleHook } from "./hooks.js";
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;