@mingxy/cerebro 1.10.0 → 1.10.1

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.10.0",
3
+ "version": "1.10.1",
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
@@ -129,7 +129,7 @@ export class OmemClient {
129
129
 
130
130
  if (!res.ok) {
131
131
  const errorBody = await res.text().catch(() => "");
132
- logWarn(`${init.method ?? "GET"} ${path} ${res.status} ${res.statusText}: ${errorBody}`);
132
+ logWarn("HTTP error", { method: init.method ?? "GET", path, status: res.status, statusText: res.statusText, errorBody });
133
133
  throw new Error(`[omem] ${res.status} ${res.statusText}${errorBody ? ": " + errorBody : ""}`);
134
134
  }
135
135
 
@@ -138,10 +138,10 @@ export class OmemClient {
138
138
  return (await res.json()) as T;
139
139
  } catch (err) {
140
140
  if ((err as Error).name === "AbortError") {
141
- logWarn(`${init.method ?? "GET"} ${path} timed out`);
141
+ logWarn("Request timed out", { method: init.method ?? "GET", path, timeoutMs: timeoutMs ?? this.getCfg("requestTimeoutMs", 15000) });
142
142
  throw new Error(`[omem] Request timed out (${timeoutMs ?? this.getCfg("requestTimeoutMs", 15000)}ms)`);
143
143
  } else {
144
- logError(`${init.method ?? "GET"} ${path} failed:`, err);
144
+ logError("Request failed", { method: init.method ?? "GET", path, error: String(err) });
145
145
  throw err;
146
146
  }
147
147
  } finally {
package/src/hooks.ts CHANGED
@@ -2,35 +2,38 @@ import type { Model, UserMessage, Part } from "@opencode-ai/sdk";
2
2
  import type { OmemClient, SearchResult } from "./client.js";
3
3
  import type { OmemPluginConfig } from "./config.js";
4
4
  import { detectKeyword, KEYWORD_NUDGE } from "./keywords.js";
5
+ import { logDebug, logError as logErr } from "./logger.js";
5
6
  import { readFile } from "node:fs/promises";
6
7
 
7
8
  const projectNameCache = new Map<string, string>();
8
9
 
9
10
  async function detectProjectName(rootPath: string): Promise<string | undefined> {
10
11
  const cached = projectNameCache.get(rootPath);
11
- if (cached !== undefined) return cached;
12
+ if (cached !== undefined) {
13
+ logDebug("detectProjectName cache hit", { rootPath, result: cached });
14
+ return cached;
15
+ }
12
16
 
13
17
  let result: string | undefined;
14
18
 
15
- // 1. AGENTS.md — first-line heading
16
19
  try {
17
20
  const agents = await readFile(`${rootPath}/AGENTS.md`, "utf-8");
18
21
  const headingMatch = agents.match(/^#\s+(.+)/m);
19
22
  if (headingMatch) {
20
23
  result = headingMatch[1].replace(/\s*\(.*?\)/g, "").trim() || undefined;
21
24
  }
25
+ logDebug("detectProjectName step1 AGENTS.md", { rootPath, result });
22
26
  } catch {}
23
27
 
24
- // 2. package.json
25
28
  if (!result) {
26
29
  try {
27
30
  const pkg = await readFile(`${rootPath}/package.json`, "utf-8");
28
31
  const nameMatch = pkg.match(/"name"\s*:\s*"([^"]+)"/);
29
32
  if (nameMatch) result = nameMatch[1].trim() || undefined;
33
+ logDebug("detectProjectName step2 package.json", { rootPath, result });
30
34
  } catch {}
31
35
  }
32
36
 
33
- // 3. Cargo.toml — name in [package]
34
37
  if (!result) {
35
38
  try {
36
39
  const cargo = await readFile(`${rootPath}/Cargo.toml`, "utf-8");
@@ -47,10 +50,10 @@ async function detectProjectName(rootPath: string): Promise<string | undefined>
47
50
  { inSection: false, name: undefined as string | undefined },
48
51
  );
49
52
  result = inPackage.name?.trim() || undefined;
53
+ logDebug("detectProjectName step3 Cargo.toml", { rootPath, result });
50
54
  } catch {}
51
55
  }
52
56
 
53
- // 4. go.mod — module last segment
54
57
  if (!result) {
55
58
  try {
56
59
  const gomod = await readFile(`${rootPath}/go.mod`, "utf-8");
@@ -59,10 +62,10 @@ async function detectProjectName(rootPath: string): Promise<string | undefined>
59
62
  const segments = modMatch[1].split("/");
60
63
  result = segments.pop()?.trim() || undefined;
61
64
  }
65
+ logDebug("detectProjectName step4 go.mod", { rootPath, result });
62
66
  } catch {}
63
67
  }
64
68
 
65
- // 5. pyproject.toml — name in [project]
66
69
  if (!result) {
67
70
  try {
68
71
  const pyproj = await readFile(`${rootPath}/pyproject.toml`, "utf-8");
@@ -79,21 +82,22 @@ async function detectProjectName(rootPath: string): Promise<string | undefined>
79
82
  { inSection: false, name: undefined as string | undefined },
80
83
  );
81
84
  result = inProject.name?.trim() || undefined;
85
+ logDebug("detectProjectName step5 pyproject.toml", { rootPath, result });
82
86
  } catch {}
83
87
  }
84
88
 
85
- // 6. composer.json
86
89
  if (!result) {
87
90
  try {
88
91
  const composer = await readFile(`${rootPath}/composer.json`, "utf-8");
89
92
  const nameMatch = composer.match(/"name"\s*:\s*"([^"]+)"/);
90
93
  if (nameMatch) result = nameMatch[1].trim() || undefined;
94
+ logDebug("detectProjectName step6 composer.json", { rootPath, result });
91
95
  } catch {}
92
96
  }
93
97
 
94
- // 7. Fallback — directory name
95
98
  if (!result) {
96
99
  result = rootPath.split("/").pop() || rootPath.split("\\").pop() || undefined;
100
+ logDebug("detectProjectName step7 fallback dirname", { rootPath, result });
97
101
  }
98
102
 
99
103
  if (result) {
@@ -427,15 +431,19 @@ export function compactingHook(client: OmemClient, containerTags: string[], tui:
427
431
  try {
428
432
  if (sdkClient && input.sessionID) {
429
433
  const sessionInfo = await sdkClient.session.get({ path: { id: input.sessionID } });
434
+ logDebug("compactingHook sessionInfo", { sessionInfo: JSON.stringify(sessionInfo) });
435
+ logDebug("compactingHook project.rootPath", { rootPath: sessionInfo?.project?.rootPath });
430
436
  projectName = sessionInfo?.project?.rootPath
431
437
  ? await detectProjectName(sessionInfo.project.rootPath)
432
438
  : undefined;
439
+ logDebug("compactingHook projectName", { projectName: String(projectName) });
433
440
  }
434
- } catch {
435
- // Detection failure should not block ingestion
441
+ } catch (e) {
442
+ logErr("compactingHook detectProjectName failed", { error: String(e) });
436
443
  }
437
444
 
438
445
  try {
446
+ logDebug("compactingHook ingestMessages called", { msgCount: messages.length, projectName: String(projectName), sessionId: effectiveSessionId });
439
447
  const result = await client.ingestMessages(messages, {
440
448
  mode: ingestMode,
441
449
  tags: [...containerTags, "auto-capture"],
@@ -443,12 +451,14 @@ export function compactingHook(client: OmemClient, containerTags: string[], tui:
443
451
  parentSessionId: isSubAgent ? input.sessionID : undefined,
444
452
  projectName: projectName,
445
453
  });
454
+ logDebug("compactingHook ingestMessages result", { result: result === null ? "null(blocked)" : "ok" });
446
455
  if (result === null) {
447
456
  showToast(tui, "🔴 Archive Failed", "Session archive blocked · check spiritual realm status", "error");
448
457
  } else {
449
458
  showToast(tui, "📦 Session Archived", `${messages.length} residual dialogues archived · merged into the realm`, "success");
450
459
  }
451
- } catch {
460
+ } catch (e) {
461
+ logErr("compactingHook ingestMessages failed", { error: String(e) });
452
462
  showToast(tui, "🔴 Archive Failed", "Session archive blocked · spiritual pulse anomaly", "error");
453
463
  }
454
464
  sessionMessages.delete(input.sessionID);
@@ -546,21 +556,27 @@ export function sessionIdleHook(
546
556
  let projectName: string | undefined;
547
557
  try {
548
558
  const sessionInfo = await sdkClient.session.get({ path: { id: sessionID } });
559
+ logDebug("sessionIdleHook sessionInfo", { sessionInfo: JSON.stringify(sessionInfo) });
560
+ logDebug("sessionIdleHook project.rootPath", { rootPath: sessionInfo?.project?.rootPath });
549
561
  sessionTitle = sessionInfo?.title;
550
562
  projectName = sessionInfo?.project?.rootPath
551
563
  ? await detectProjectName(sessionInfo.project.rootPath)
552
564
  : undefined;
565
+ logDebug("sessionIdleHook projectName", { projectName: String(projectName) });
553
566
  } catch (e) {
554
- // 获取失败不影响主流程
567
+ logErr("sessionIdleHook detectProjectName failed", { error: String(e) });
555
568
  }
556
569
 
557
570
  try {
571
+ logDebug("sessionIdleHook sessionIngest called", { msgCount: conversationMessages.length, projectName: String(projectName), sessionId: sessionID, title: String(sessionTitle) });
558
572
  await omemClient.sessionIngest(conversationMessages, sessionID, agentId, sessionTitle, projectName);
573
+ logDebug("sessionIdleHook sessionIngest ok");
559
574
  for (const id of newMessageIds) {
560
575
  processedMessageIds.add(id);
561
576
  }
562
577
  showToast(tui, "🧠 Memory Sealed", `${conversationMessages.length} dialogues captured · entrusted to the heavens for refinement`, "success");
563
578
  } catch (err) {
579
+ logErr("sessionIdleHook sessionIngest failed", { error: String(err) });
564
580
  showToast(tui, "🔴 Session Capture Failed", String(err).substring(0, 100), "error");
565
581
  }
566
582
  } catch (err) {
package/src/logger.ts CHANGED
@@ -5,42 +5,46 @@ import { join } from "node:path";
5
5
  const LOG_DIR = join(homedir(), ".config", "ourmem");
6
6
  const LOG_FILE = join(LOG_DIR, "plugin.log");
7
7
 
8
+ const START_TIME = Date.now();
9
+
8
10
  function ensureLogDir(): void {
9
11
  if (!existsSync(LOG_DIR)) {
10
12
  try {
11
13
  mkdirSync(LOG_DIR, { recursive: true });
12
- } catch {
13
- // silently fail if we can't create log directory
14
- }
14
+ } catch {}
15
15
  }
16
16
  }
17
17
 
18
- function writeLog(level: string, ...args: unknown[]): void {
18
+ function writeLog(level: string, message: string, fields?: Record<string, unknown>): void {
19
19
  ensureLogDir();
20
- const timestamp = new Date().toISOString();
21
- const message = args
22
- .map((a) => (typeof a === "string" ? a : JSON.stringify(a)))
23
- .join(" ");
24
- const line = `[${timestamp}] [${level}] ${message}\n`;
25
- try {
26
- appendFileSync(LOG_FILE, line);
27
- } catch {
28
- // silently fail if we can't write to log file
20
+ const now = new Date();
21
+ const ts = now.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
22
+ const offset = Date.now() - START_TIME;
23
+ const parts = [`${level.padEnd(5)} ${ts} +${offset}ms service=cerebro`];
24
+ if (fields) {
25
+ for (const [k, v] of Object.entries(fields)) {
26
+ const val = typeof v === "string" ? v : JSON.stringify(v);
27
+ parts.push(`${k}=${val}`);
28
+ }
29
29
  }
30
+ parts.push(message);
31
+ try {
32
+ appendFileSync(LOG_FILE, parts.join(" ") + "\n");
33
+ } catch {}
30
34
  }
31
35
 
32
- export function logInfo(...args: unknown[]): void {
33
- writeLog("INFO", ...args);
36
+ export function logInfo(message: string, fields?: Record<string, unknown>): void {
37
+ writeLog("INFO", message, fields);
34
38
  }
35
39
 
36
- export function logWarn(...args: unknown[]): void {
37
- writeLog("WARN", ...args);
40
+ export function logWarn(message: string, fields?: Record<string, unknown>): void {
41
+ writeLog("WARN", message, fields);
38
42
  }
39
43
 
40
- export function logError(...args: unknown[]): void {
41
- writeLog("ERROR", ...args);
44
+ export function logError(message: string, fields?: Record<string, unknown>): void {
45
+ writeLog("ERROR", message, fields);
42
46
  }
43
47
 
44
- export function logDebug(...args: unknown[]): void {
45
- writeLog("DEBUG", ...args);
48
+ export function logDebug(message: string, fields?: Record<string, unknown>): void {
49
+ writeLog("DEBUG", message, fields);
46
50
  }