@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/cerebro.example.jsonc +71 -0
- package/dist/client.d.ts +2 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -15
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +30 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +101 -46
- package/dist/config.js.map +1 -1
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +57 -33
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +4 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +42 -23
- package/dist/logger.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1 -1
- package/dist/tools.js.map +1 -1
- package/package.json +47 -47
- package/src/client.ts +13 -14
- package/src/config.ts +207 -99
- package/src/hooks.ts +29 -20
- package/src/index.ts +13 -13
- package/src/logger.ts +65 -65
- package/src/tools.ts +3 -3
- package/omem.example.jsonc +0 -22
package/src/hooks.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Model, UserMessage, Part } from "@opencode-ai/sdk";
|
|
2
|
-
import type {
|
|
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
|
-
"<
|
|
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
|
-
"</
|
|
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
|
-
"<
|
|
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
|
-
"</
|
|
228
|
+
"</cerebro-context>",
|
|
229
229
|
].join("\n");
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
export function autoRecallHook(client:
|
|
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
|
-
"<
|
|
271
|
+
"<cerebro-profile>",
|
|
265
272
|
JSON.stringify(profile, null, 2),
|
|
266
|
-
"</
|
|
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("[
|
|
368
|
+
if (errMsg.includes("[cerebro]")) {
|
|
359
369
|
// Server returned error (500, etc.) with details
|
|
360
|
-
const cleanMsg = errMsg.replace(/^\[
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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/
|
|
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
|
|
83
|
+
const cerebroClient = new CerebroClient(config.connection.apiUrl, config.connection.apiKey, config);
|
|
84
84
|
|
|
85
85
|
// 启动时检测连接状态
|
|
86
86
|
try {
|
|
87
|
-
await
|
|
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("[
|
|
94
|
-
const cleanMsg = errMsg.replace(/^\[
|
|
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(
|
|
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(
|
|
135
|
-
"experimental.session.compacting": compactingHook(
|
|
136
|
-
tool: buildTools(
|
|
137
|
-
event: sessionIdleHook(
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
}),
|
package/omem.example.jsonc
DELETED
|
@@ -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
|
-
}
|