@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 +1 -1
- package/src/client.ts +3 -3
- package/src/hooks.ts +28 -12
- package/src/logger.ts +25 -21
package/package.json
CHANGED
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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,
|
|
18
|
+
function writeLog(level: string, message: string, fields?: Record<string, unknown>): void {
|
|
19
19
|
ensureLogDir();
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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(
|
|
33
|
-
writeLog("INFO",
|
|
36
|
+
export function logInfo(message: string, fields?: Record<string, unknown>): void {
|
|
37
|
+
writeLog("INFO", message, fields);
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
export function logWarn(
|
|
37
|
-
writeLog("WARN",
|
|
40
|
+
export function logWarn(message: string, fields?: Record<string, unknown>): void {
|
|
41
|
+
writeLog("WARN", message, fields);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export function logError(
|
|
41
|
-
writeLog("ERROR",
|
|
44
|
+
export function logError(message: string, fields?: Record<string, unknown>): void {
|
|
45
|
+
writeLog("ERROR", message, fields);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
export function logDebug(
|
|
45
|
-
writeLog("DEBUG",
|
|
48
|
+
export function logDebug(message: string, fields?: Record<string, unknown>): void {
|
|
49
|
+
writeLog("DEBUG", message, fields);
|
|
46
50
|
}
|