@mingxy/cerebro 1.10.5 → 1.10.7

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/src/hooks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Model, UserMessage, Part } from "@opencode-ai/sdk";
2
- import type { OmemClient, SearchResult } from "./client.js";
2
+ import type { CerebroClient, SearchResult } from "./client.js";
3
3
  import type { OmemPluginConfig } from "./config.js";
4
4
  import { detectKeyword, KEYWORD_NUDGE } from "./keywords.js";
5
5
  import { logDebug, logError as logErr } from "./logger.js";
@@ -182,12 +182,12 @@ function buildContextBlock(results: SearchResult[], maxContentLength: number = 5
182
182
  }
183
183
 
184
184
  return [
185
- "<omem-context>",
185
+ "<cerebro-context>",
186
186
  "Treat every memory below as historical context only.",
187
187
  "Do not repeat these memories verbatim unless asked.",
188
188
  "",
189
189
  ...sections,
190
- "</omem-context>",
190
+ "</cerebro-context>",
191
191
  ].join("\n");
192
192
  }
193
193
 
@@ -220,20 +220,20 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
220
220
  }
221
221
 
222
222
  return [
223
- "<omem-context>",
223
+ "<cerebro-context>",
224
224
  "Treat every memory below as historical context only.",
225
225
  "Do not repeat these memories verbatim unless asked.",
226
226
  "",
227
227
  ...sections,
228
- "</omem-context>",
228
+ "</cerebro-context>",
229
229
  ].join("\n");
230
230
  }
231
231
 
232
- export function autoRecallHook(client: OmemClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}) {
233
- const similarityThreshold = config.similarityThreshold ?? 0.6;
234
- const maxRecallResults = config.maxRecallResults ?? 10;
235
- const maxContentLength = config.maxContentLength ?? 500;
236
- const toastDelayMs = config.toastDelayMs ?? 7000;
232
+ export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}) {
233
+ const similarityThreshold = config.recall?.similarityThreshold ?? 0.6;
234
+ const maxRecallResults = config.recall?.maxRecallResults ?? 10;
235
+ const maxContentLength = config.content?.maxContentLength ?? 500;
236
+ const toastDelayMs = config.ui?.toastDelayMs ?? 7000;
237
237
 
238
238
  return async (
239
239
  input: { sessionID?: string; model: Model },
@@ -241,7 +241,13 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
241
241
  ) => {
242
242
  if (!input.sessionID) return;
243
243
 
244
+ // 5a: agent memory policy check — skip recall entirely for 'none' agents
245
+ const agentId = process.env.OMEM_AGENT_ID || "opencode";
246
+ const policy = config.agentMemoryPolicy?.[agentId] ?? config.defaultPolicy ?? "readonly";
247
+ if (policy === "none") return;
248
+
244
249
  try {
250
+ logDebug("autoRecallHook start", { sessionId: input.sessionID, agentId, policy });
245
251
  const messages = sessionMessages.get(input.sessionID) ?? [];
246
252
  const userMessages = messages.filter((m) => m.role === "user");
247
253
  const rawQuery = userMessages[userMessages.length - 1]?.content || firstMessages.get(input.sessionID) || "";
@@ -255,15 +261,16 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
255
261
  showToast(tui, "🧠 Cerebro Service Unavailable", "Unable to reach memory API · check connection", "error", toastDelayMs);
256
262
  return;
257
263
  }
264
+ logDebug("autoRecallHook shouldRecall result", { shouldRecall: shouldRecallRes.should_recall, confidence: shouldRecallRes.confidence, memCount: shouldRecallRes.memories?.length ?? 0, clustered: !!shouldRecallRes.clustered });
258
265
 
259
266
  const profile = await client.getProfile();
260
267
  let profileInjected = false;
261
268
  let profileCountText = "";
262
269
  if (profile && !profileInjectedSessions.has(input.sessionID)) {
263
270
  const profileBlock = [
264
- "<omem-profile>",
271
+ "<cerebro-profile>",
265
272
  JSON.stringify(profile, null, 2),
266
- "</omem-profile>",
273
+ "</cerebro-profile>",
267
274
  ].join("\n");
268
275
  output.system.push(profileBlock);
269
276
  profileInjected = true;
@@ -272,6 +279,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
272
279
  const dynamicCount = p?.dynamic_context?.length ?? 0;
273
280
  const staticCount = p?.static_facts?.length ?? 0;
274
281
  profileCountText = `Dynamic(${dynamicCount}) · Static(${staticCount})`;
282
+ logDebug("autoRecallHook profile injected", { dynamicCount, staticCount });
275
283
  }
276
284
 
277
285
  if (!shouldRecallRes.should_recall) {
@@ -286,6 +294,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
286
294
 
287
295
  const existingIds = injectedMemoryIds.get(input.sessionID) ?? new Set<string>();
288
296
  const newResults = results.filter((r) => !existingIds.has(r.memory.id));
297
+ logDebug("autoRecallHook dedup", { totalResults: results.length, existingCount: existingIds.size, newCount: newResults.length });
289
298
  if (newResults.length === 0) {
290
299
  if (profileInjected) {
291
300
  showToast(tui, "👨 Profile Injected", `${profileCountText} · all memories already injected`, "success", toastDelayMs);
@@ -302,6 +311,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
302
311
 
303
312
  const newIds = newResults.map((r) => r.memory.id);
304
313
  injectedMemoryIds.set(input.sessionID, new Set([...existingIds, ...newIds]));
314
+ logDebug("autoRecallHook injection complete", { newIds: newIds.length, clustered: !!clustered });
305
315
 
306
316
  const recordResult = await client.recordSessionRecall(
307
317
  input.sessionID,
@@ -355,9 +365,9 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
355
365
  }
356
366
  } catch (err) {
357
367
  const errMsg = err instanceof Error ? err.message : String(err);
358
- if (errMsg.includes("[omem]")) {
368
+ if (errMsg.includes("[cerebro]")) {
359
369
  // Server returned error (500, etc.) with details
360
- const cleanMsg = errMsg.replace(/^\[omem\]\s*/, "");
370
+ const cleanMsg = errMsg.replace(/^\[cerebro\]\s*/, "");
361
371
  if (cleanMsg.startsWith("500")) {
362
372
  showToast(tui, "🧠 Cerebro Server Error", cleanMsg.substring(0, 200), "error");
363
373
  } else if (cleanMsg.includes("timed out")) {
@@ -374,7 +384,7 @@ export function autoRecallHook(client: OmemClient, containerTags: string[], tui:
374
384
  };
375
385
  }
376
386
 
377
- export function keywordDetectionHook(_client: OmemClient, _containerTags: string[], threshold: number, _tui: any, _ingestMode: "smart" | "raw" = "smart") {
387
+ export function keywordDetectionHook(_client: CerebroClient, _containerTags: string[], threshold: number, _tui: any, _ingestMode: "smart" | "raw" = "smart") {
378
388
  return async (
379
389
  input: { sessionID: string; messageID?: string },
380
390
  output: { message: UserMessage; parts: Part[] },
@@ -392,6 +402,7 @@ export function keywordDetectionHook(_client: OmemClient, _containerTags: string
392
402
 
393
403
  if (detectKeyword(textContent)) {
394
404
  keywordDetectedSessions.add(input.sessionID);
405
+ logDebug("keywordDetectionHook triggered", { sessionId: input.sessionID });
395
406
  }
396
407
 
397
408
  if (!sessionMessages.has(input.sessionID)) {
@@ -411,7 +422,7 @@ export function keywordDetectionHook(_client: OmemClient, _containerTags: string
411
422
  };
412
423
  }
413
424
 
414
- export function compactingHook(client: OmemClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any) {
425
+ export function compactingHook(client: CerebroClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any) {
415
426
  return async (
416
427
  input: { sessionID?: string },
417
428
  output: { context: string[]; prompt?: string },
@@ -424,7 +435,6 @@ export function compactingHook(client: OmemClient, containerTags: string[], tui:
424
435
  if (messages.length > 0) {
425
436
  // Use main session ID for sub-agent sessions so memories merge into the main session
426
437
  const effectiveSessionId = (getMainSessionId?.() || input.sessionID);
427
- const isSubAgent = getMainSessionId?.() && input.sessionID !== getMainSessionId();
428
438
 
429
439
  // Detect project name from session info
430
440
  let projectName: string | undefined;
@@ -448,7 +458,6 @@ export function compactingHook(client: OmemClient, containerTags: string[], tui:
448
458
  mode: ingestMode,
449
459
  tags: [...containerTags, "auto-capture"],
450
460
  sessionId: effectiveSessionId,
451
- parentSessionId: isSubAgent ? getMainSessionId?.() : undefined,
452
461
  projectName: projectName,
453
462
  });
454
463
  logDebug("compactingHook ingestMessages result", { result: result === null ? "null(blocked)" : "ok" });
@@ -481,7 +490,7 @@ const processedMessageIds = new Set<string>();
481
490
  const pluginStartTime = Date.now();
482
491
 
483
492
  export function sessionIdleHook(
484
- omemClient: OmemClient,
493
+ cerebroClient: CerebroClient,
485
494
  _containerTags: string[],
486
495
  tui: any,
487
496
  sdkClient: any,
@@ -569,7 +578,7 @@ export function sessionIdleHook(
569
578
 
570
579
  try {
571
580
  logDebug("sessionIdleHook sessionIngest called", { msgCount: conversationMessages.length, projectName: String(projectName), sessionId: sessionID, title: String(sessionTitle) });
572
- await omemClient.sessionIngest(conversationMessages, sessionID, agentId, sessionTitle, projectName);
581
+ await cerebroClient.sessionIngest(conversationMessages, sessionID, agentId, sessionTitle, projectName);
573
582
  logDebug("sessionIdleHook sessionIngest ok");
574
583
  for (const id of newMessageIds) {
575
584
  processedMessageIds.add(id);
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import { readFileSync, writeFileSync } from "node:fs";
3
3
  import { join, dirname } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
- import { OmemClient } from "./client.js";
6
+ import { CerebroClient } from "./client.js";
7
7
  import { autoRecallHook, compactingHook, keywordDetectionHook, sessionIdleHook } from "./hooks.js";
8
8
  import { getUserTag, getProjectTag } from "./tags.js";
9
9
  import { buildTools } from "./tools.js";
@@ -74,24 +74,24 @@ const OmemPlugin: Plugin = async (input) => {
74
74
  let overrides: Record<string, unknown> = {};
75
75
  try {
76
76
  const ocCfg = JSON.parse(readFileSync(join(directory, "opencode.json"), "utf-8"));
77
- const pc = ocCfg?.plugin_config?.["@mingxy/omem"] || ocCfg?.plugin_config?.["@ourmem/opencode"];
77
+ const pc = ocCfg?.plugin_config?.["@mingxy/cerebro"];
78
78
  if (pc) overrides = pc;
79
79
  } catch {}
80
80
 
81
81
  const config = loadPluginConfig(overrides as any);
82
82
 
83
- const omemClient = new OmemClient(config.apiUrl, config.apiKey, config);
83
+ const cerebroClient = new CerebroClient(config.connection.apiUrl, config.connection.apiKey, config);
84
84
 
85
85
  // 启动时检测连接状态
86
86
  try {
87
- await omemClient.getStats();
87
+ await cerebroClient.getStats();
88
88
  showToast(tui, "🧠 Cerebro · Connected", `Version v${pluginVersion}`, "success", 6000);
89
- logInfo(`Connected to ${config.apiUrl}`);
89
+ logInfo(`Connected to ${config.connection.apiUrl}`);
90
90
  } catch (err) {
91
91
  const errMsg = err instanceof Error ? err.message : String(err);
92
92
  logError(`Connection failed: ${errMsg}`);
93
- if (errMsg.includes("[omem]")) {
94
- const cleanMsg = errMsg.replace(/^\[omem\]\s*/, "");
93
+ if (errMsg.includes("[cerebro]")) {
94
+ const cleanMsg = errMsg.replace(/^\[cerebro\]\s*/, "");
95
95
  showToast(
96
96
  tui,
97
97
  `🧠 Cerebro v${pluginVersion} · Server Error`,
@@ -103,7 +103,7 @@ const OmemPlugin: Plugin = async (input) => {
103
103
  showToast(
104
104
  tui,
105
105
  `🧠 Cerebro v${pluginVersion} · Connection Failed`,
106
- `Unable to reach ${config.apiUrl}`,
106
+ `Unable to reach ${config.connection.apiUrl}`,
107
107
  "error",
108
108
  8000
109
109
  );
@@ -117,7 +117,7 @@ const OmemPlugin: Plugin = async (input) => {
117
117
 
118
118
  let currentSessionId: string | undefined;
119
119
 
120
- const recallHook = autoRecallHook(omemClient, containerTags, tui, config);
120
+ const recallHook = autoRecallHook(cerebroClient, containerTags, tui, config);
121
121
 
122
122
  return {
123
123
  config: async (cfg: any) => {
@@ -131,10 +131,10 @@ const OmemPlugin: Plugin = async (input) => {
131
131
  if (input.sessionID) currentSessionId = input.sessionID;
132
132
  return recallHook(input, output);
133
133
  },
134
- "chat.message": keywordDetectionHook(omemClient, containerTags, config.autoCaptureThreshold, tui, config.ingestMode),
135
- "experimental.session.compacting": compactingHook(omemClient, containerTags, tui, config.ingestMode, isAutoStoreEnabled, () => currentSessionId, client),
136
- tool: buildTools(omemClient, containerTags, { agentId, getSessionId: () => currentSessionId }),
137
- event: sessionIdleHook(omemClient, containerTags, tui, client, config.ingestMode, config.autoCaptureThreshold, () => currentSessionId, isAutoStoreEnabled, agentId),
134
+ "chat.message": keywordDetectionHook(cerebroClient, containerTags, config.ingest.autoCaptureThreshold, tui, config.ingest.ingestMode),
135
+ "experimental.session.compacting": compactingHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => currentSessionId, client),
136
+ tool: buildTools(cerebroClient, containerTags, { agentId, getSessionId: () => currentSessionId }),
137
+ event: sessionIdleHook(cerebroClient, containerTags, tui, client, config.ingest.ingestMode, config.ingest.autoCaptureThreshold, () => currentSessionId, isAutoStoreEnabled, agentId),
138
138
  "shell.env": async (_input: any, output: any) => {
139
139
  if (directory) {
140
140
  output.env.OMEM_PROJECT_DIR = directory;
package/src/logger.ts CHANGED
@@ -1,65 +1,65 @@
1
- import { appendFileSync, mkdirSync, existsSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { loadPluginConfig } from "./config.js";
4
-
5
- const LEVEL_MAP: Record<string, number> = {
6
- DEBUG: 0,
7
- INFO: 1,
8
- WARN: 2,
9
- ERROR: 3,
10
- };
11
-
12
- const cfg = loadPluginConfig();
13
- const MIN_LEVEL = LEVEL_MAP[cfg.logLevel] ?? LEVEL_MAP.INFO;
14
- const LOG_DIR = cfg.logDir;
15
- const LOG_FILE = join(LOG_DIR, "plugin.log");
16
- const LOG_ENABLED = cfg.logEnabled;
17
-
18
- let lastLogTime = Date.now();
19
-
20
- function ensureLogDir(): void {
21
- if (!existsSync(LOG_DIR)) {
22
- try {
23
- mkdirSync(LOG_DIR, { recursive: true });
24
- } catch {}
25
- }
26
- }
27
-
28
- function writeLog(level: string, message: string, fields?: Record<string, unknown>): void {
29
- if (!LOG_ENABLED) return;
30
- const lvl = LEVEL_MAP[level] ?? 0;
31
- if (lvl < MIN_LEVEL) return;
32
- ensureLogDir();
33
- const now = new Date();
34
- const nowMs = now.getTime();
35
- const delta = ((nowMs - lastLogTime) / 1000).toFixed(2);
36
- lastLogTime = nowMs;
37
- const ts = now.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
38
- const parts = [`${level.padEnd(5)} ${ts} +${delta}s service=cerebro`];
39
- if (fields) {
40
- for (const [k, v] of Object.entries(fields)) {
41
- const val = typeof v === "string" ? v : JSON.stringify(v);
42
- parts.push(`${k}=${val}`);
43
- }
44
- }
45
- parts.push(message);
46
- try {
47
- appendFileSync(LOG_FILE, parts.join(" ") + "\n");
48
- } catch {}
49
- }
50
-
51
- export function logInfo(message: string, fields?: Record<string, unknown>): void {
52
- writeLog("INFO", message, fields);
53
- }
54
-
55
- export function logWarn(message: string, fields?: Record<string, unknown>): void {
56
- writeLog("WARN", message, fields);
57
- }
58
-
59
- export function logError(message: string, fields?: Record<string, unknown>): void {
60
- writeLog("ERROR", message, fields);
61
- }
62
-
63
- export function logDebug(message: string, fields?: Record<string, unknown>): void {
64
- writeLog("DEBUG", message, fields);
65
- }
1
+ import { appendFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { loadPluginConfig } from "./config.js";
4
+
5
+ const LEVEL_MAP: Record<string, number> = {
6
+ DEBUG: 0,
7
+ INFO: 1,
8
+ WARN: 2,
9
+ ERROR: 3,
10
+ };
11
+
12
+ const cfg = loadPluginConfig();
13
+ const MIN_LEVEL = LEVEL_MAP[cfg.logging.logLevel] ?? LEVEL_MAP.INFO;
14
+ const LOG_DIR = cfg.logging.logDir;
15
+ const LOG_FILE = join(LOG_DIR, "plugin.log");
16
+ const LOG_ENABLED = cfg.logging.logEnabled;
17
+
18
+ let lastLogTime = Date.now();
19
+
20
+ function ensureLogDir(): void {
21
+ if (!existsSync(LOG_DIR)) {
22
+ try {
23
+ mkdirSync(LOG_DIR, { recursive: true });
24
+ } catch {}
25
+ }
26
+ }
27
+
28
+ function writeLog(level: string, message: string, fields?: Record<string, unknown>): void {
29
+ if (!LOG_ENABLED) return;
30
+ const lvl = LEVEL_MAP[level] ?? 0;
31
+ if (lvl < MIN_LEVEL) return;
32
+ ensureLogDir();
33
+ const now = new Date();
34
+ const nowMs = now.getTime();
35
+ const delta = ((nowMs - lastLogTime) / 1000).toFixed(2);
36
+ lastLogTime = nowMs;
37
+ const ts = now.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
38
+ const parts = [`${level.padEnd(5)} ${ts} +${delta}s service=cerebro`];
39
+ if (fields) {
40
+ for (const [k, v] of Object.entries(fields)) {
41
+ const val = typeof v === "string" ? v : JSON.stringify(v);
42
+ parts.push(`${k}=${val}`);
43
+ }
44
+ }
45
+ parts.push(message);
46
+ try {
47
+ appendFileSync(LOG_FILE, parts.join(" ") + "\n");
48
+ } catch {}
49
+ }
50
+
51
+ export function logInfo(message: string, fields?: Record<string, unknown>): void {
52
+ writeLog("INFO", message, fields);
53
+ }
54
+
55
+ export function logWarn(message: string, fields?: Record<string, unknown>): void {
56
+ writeLog("WARN", message, fields);
57
+ }
58
+
59
+ export function logError(message: string, fields?: Record<string, unknown>): void {
60
+ writeLog("ERROR", message, fields);
61
+ }
62
+
63
+ export function logDebug(message: string, fields?: Record<string, unknown>): void {
64
+ writeLog("DEBUG", message, fields);
65
+ }
package/src/tools.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
- import type { OmemClient } from "./client.js";
2
+ import type { CerebroClient } from "./client.js";
3
3
  import { isAutoStoreEnabled, setAutoStoreEnabled } from "./index.js";
4
4
 
5
5
  function extractMemoryIds(result: unknown): string[] {
@@ -26,7 +26,7 @@ export interface ToolContext {
26
26
  getSessionId: () => string | undefined;
27
27
  }
28
28
 
29
- export function buildTools(client: OmemClient, containerTags: string[], context: ToolContext) {
29
+ export function buildTools(client: CerebroClient, containerTags: string[], context: ToolContext) {
30
30
  return {
31
31
  memory_store: tool({
32
32
  description:
@@ -95,7 +95,7 @@ export function buildTools(client: OmemClient, containerTags: string[], context:
95
95
  args.visibility,
96
96
  args.category,
97
97
  );
98
- if (!result) return JSON.stringify({ ok: false, error: "The omem server may be unavailable." });
98
+ if (!result) return JSON.stringify({ ok: false, error: "The Cerebro server may be unavailable." });
99
99
  return JSON.stringify({ ok: true, id: result.id, tags: result.tags });
100
100
  },
101
101
  }),
@@ -1,22 +0,0 @@
1
- {
2
- "apiUrl": "https://www.mengxy.cc",
3
- "apiKey": "your-tenant-id",
4
-
5
- "requestTimeoutMs": 15000,
6
-
7
- "maxQueryLength": 200,
8
- "maxContentChars": 30000,
9
- "maxContentLength": 500,
10
-
11
- "autoCaptureThreshold": 5,
12
- "ingestMode": "smart",
13
-
14
- "similarityThreshold": 0.4,
15
- "maxRecallResults": 10,
16
-
17
- "toastDelayMs": 7000,
18
-
19
- "logEnabled": true,
20
- "logLevel": "INFO",
21
- "logDir": "~/.config/cerebro"
22
- }