@mingxy/cerebro 1.18.20 → 1.19.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 +9 -71
- package/src/config.ts +24 -59
- package/src/hooks.ts +27 -14
- package/src/index.ts +15 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mingxy/cerebro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"description": "Cerebro persistent memory plugin for OpenCode — auto-recall, auto-capture, 9 memory tools with clustering, project-scoped memory isolation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { logWarn, logError } from "./logger.js";
|
|
2
|
+
import type { CerebroPluginConfig } from "./config.js";
|
|
3
3
|
|
|
4
4
|
function sanitizeContent(text: string, maxLen: number): string {
|
|
5
5
|
let clean = text.replace(/<[\w-]+[^>]*>[\s\S]*?<\/[\w-]+>/g, "");
|
|
@@ -42,25 +42,6 @@ export interface ListResponse {
|
|
|
42
42
|
offset: number;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
export interface DiscardedItem {
|
|
47
|
-
memory_id: string;
|
|
48
|
-
content: string;
|
|
49
|
-
score: number;
|
|
50
|
-
refine_relevance?: string;
|
|
51
|
-
refine_reasoning?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ShouldRecallResponse {
|
|
55
|
-
should_recall: boolean;
|
|
56
|
-
query?: string;
|
|
57
|
-
reason?: string;
|
|
58
|
-
similarity_score?: number;
|
|
59
|
-
confidence?: number;
|
|
60
|
-
memories?: SearchResult[];
|
|
61
|
-
discarded?: DiscardedItem[];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
45
|
export interface PreferenceDto {
|
|
65
46
|
id: string;
|
|
66
47
|
slot: string;
|
|
@@ -101,16 +82,16 @@ export class CerebroClient {
|
|
|
101
82
|
constructor(
|
|
102
83
|
private baseUrl: string,
|
|
103
84
|
private apiKey: string,
|
|
104
|
-
private config?: Partial<
|
|
85
|
+
private config?: Partial<CerebroPluginConfig>,
|
|
105
86
|
) {
|
|
106
87
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
107
88
|
}
|
|
108
89
|
|
|
109
|
-
private getCfg<S extends keyof
|
|
110
|
-
section: S, key: K, fallback:
|
|
111
|
-
):
|
|
90
|
+
private getCfg<S extends keyof CerebroPluginConfig, K extends string & keyof CerebroPluginConfig[S]>(
|
|
91
|
+
section: S, key: K, fallback: CerebroPluginConfig[S][K],
|
|
92
|
+
): CerebroPluginConfig[S][K] {
|
|
112
93
|
const sec = this.config?.[section] as Record<string, unknown> | undefined;
|
|
113
|
-
return (sec?.[key] ?? fallback) as
|
|
94
|
+
return (sec?.[key] ?? fallback) as CerebroPluginConfig[S][K];
|
|
114
95
|
}
|
|
115
96
|
|
|
116
97
|
private async request<T>(
|
|
@@ -202,7 +183,7 @@ export class CerebroClient {
|
|
|
202
183
|
category?: string,
|
|
203
184
|
projectPath?: string,
|
|
204
185
|
): Promise<MemoryDto | null> {
|
|
205
|
-
const safeContent = sanitizeContent(content, this.getCfg("content", "
|
|
186
|
+
const safeContent = sanitizeContent(content, this.getCfg("content", "maxContentLength", 3000));
|
|
206
187
|
return this.post<MemoryDto>("/v1/memories", {
|
|
207
188
|
content: safeContent,
|
|
208
189
|
tags,
|
|
@@ -261,7 +242,7 @@ export class CerebroClient {
|
|
|
261
242
|
): Promise<unknown> {
|
|
262
243
|
const safeMessages = messages.map(m => ({
|
|
263
244
|
role: m.role,
|
|
264
|
-
content: sanitizeContent(m.content, this.getCfg("content", "
|
|
245
|
+
content: sanitizeContent(m.content, this.getCfg("content", "maxContentLength", 3000)),
|
|
265
246
|
}));
|
|
266
247
|
return this.post("/v1/memories", {
|
|
267
248
|
messages: safeMessages,
|
|
@@ -362,49 +343,6 @@ export class CerebroClient {
|
|
|
362
343
|
);
|
|
363
344
|
}
|
|
364
345
|
|
|
365
|
-
async shouldRecall(
|
|
366
|
-
query_text: string,
|
|
367
|
-
last_query_text: string | undefined,
|
|
368
|
-
session_id: string,
|
|
369
|
-
similarity_threshold?: number,
|
|
370
|
-
max_results?: number,
|
|
371
|
-
project_tags?: string[],
|
|
372
|
-
conversation_context?: string[],
|
|
373
|
-
recall_overrides?: {
|
|
374
|
-
fetch_multiplier?: number;
|
|
375
|
-
topk_cap_multiplier?: number;
|
|
376
|
-
mmr_jaccard_threshold?: number;
|
|
377
|
-
mmr_penalty_factor?: number;
|
|
378
|
-
phase2_multiplier?: number;
|
|
379
|
-
llm_max_eval?: number;
|
|
380
|
-
refine_strategy?: string;
|
|
381
|
-
skip_llm_gate?: boolean;
|
|
382
|
-
},
|
|
383
|
-
projectPath?: string,
|
|
384
|
-
): Promise<ShouldRecallResponse | null> {
|
|
385
|
-
const res = await this.post<ShouldRecallResponse>("/v1/should-recall", {
|
|
386
|
-
query_text,
|
|
387
|
-
last_query_text,
|
|
388
|
-
session_id,
|
|
389
|
-
similarity_threshold,
|
|
390
|
-
max_results,
|
|
391
|
-
project_tags,
|
|
392
|
-
conversation_context,
|
|
393
|
-
...recall_overrides,
|
|
394
|
-
project_path: projectPath,
|
|
395
|
-
}, 20_000);
|
|
396
|
-
logInfo("shouldRecall raw response", {
|
|
397
|
-
should_recall: res?.should_recall,
|
|
398
|
-
memCount: res?.memories?.length ?? 0,
|
|
399
|
-
discardedCount: res?.discarded?.length ?? 0,
|
|
400
|
-
confidence: res?.confidence,
|
|
401
|
-
reason: res?.reason,
|
|
402
|
-
rawKeys: res ? Object.keys(res) : "null",
|
|
403
|
-
rawJSON: JSON.stringify(res).slice(0, 500),
|
|
404
|
-
});
|
|
405
|
-
return res;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
346
|
async updateProfileInjected(
|
|
409
347
|
event_id: string,
|
|
410
348
|
profile_injected: boolean,
|
package/src/config.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
// ── Nested config interface ──────────────────────────────────────────
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface CerebroPluginConfig {
|
|
8
8
|
connection: {
|
|
9
9
|
apiUrl: string;
|
|
10
10
|
apiKey: string;
|
|
@@ -15,21 +15,16 @@ export interface OmemPluginConfig {
|
|
|
15
15
|
maxContentChars: number;
|
|
16
16
|
maxContentLength: number;
|
|
17
17
|
};
|
|
18
|
+
injection: {
|
|
19
|
+
recentCount: number;
|
|
20
|
+
searchCount: number;
|
|
21
|
+
recentTruncateChars: number;
|
|
22
|
+
searchTruncateChars: number;
|
|
23
|
+
};
|
|
18
24
|
ingest: {
|
|
19
25
|
autoCaptureThreshold: number;
|
|
20
26
|
ingestMode: "smart" | "raw";
|
|
21
27
|
};
|
|
22
|
-
recall: {
|
|
23
|
-
similarityThreshold: number;
|
|
24
|
-
maxRecallResults: number;
|
|
25
|
-
fetchMultiplier: number;
|
|
26
|
-
topkCapMultiplier: number;
|
|
27
|
-
mmrJaccardThreshold: number;
|
|
28
|
-
mmrPenaltyFactor: number;
|
|
29
|
-
phase2Multiplier: number;
|
|
30
|
-
llmMaxEval: number;
|
|
31
|
-
refineStrategy: "strict" | "balanced" | "loose";
|
|
32
|
-
};
|
|
33
28
|
logging: {
|
|
34
29
|
logEnabled: boolean;
|
|
35
30
|
logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
@@ -42,16 +37,13 @@ export interface OmemPluginConfig {
|
|
|
42
37
|
enabled?: boolean;
|
|
43
38
|
port?: number;
|
|
44
39
|
};
|
|
45
|
-
profile?: {
|
|
46
|
-
ttlMs?: number;
|
|
47
|
-
};
|
|
48
40
|
agentMemoryPolicy?: Record<string, "none" | "readonly" | "readwrite">;
|
|
49
41
|
defaultPolicy?: "none" | "readonly" | "readwrite";
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
// ── Defaults ─────────────────────────────────────────────────────────
|
|
53
45
|
|
|
54
|
-
const DEFAULTS:
|
|
46
|
+
const DEFAULTS: CerebroPluginConfig = {
|
|
55
47
|
connection: {
|
|
56
48
|
apiUrl: "https://www.mengxy.cc",
|
|
57
49
|
apiKey: "",
|
|
@@ -60,23 +52,18 @@ const DEFAULTS: OmemPluginConfig = {
|
|
|
60
52
|
content: {
|
|
61
53
|
maxQueryLength: 200,
|
|
62
54
|
maxContentChars: 30000,
|
|
63
|
-
maxContentLength:
|
|
55
|
+
maxContentLength: 3000,
|
|
56
|
+
},
|
|
57
|
+
injection: {
|
|
58
|
+
recentCount: 5,
|
|
59
|
+
searchCount: 10,
|
|
60
|
+
recentTruncateChars: 0, // 0 = 不截断
|
|
61
|
+
searchTruncateChars: 0, // 0 = 不截断
|
|
64
62
|
},
|
|
65
63
|
ingest: {
|
|
66
64
|
autoCaptureThreshold: 5,
|
|
67
65
|
ingestMode: "smart",
|
|
68
66
|
},
|
|
69
|
-
recall: {
|
|
70
|
-
similarityThreshold: 0.4,
|
|
71
|
-
maxRecallResults: 10,
|
|
72
|
-
fetchMultiplier: 3,
|
|
73
|
-
topkCapMultiplier: 2,
|
|
74
|
-
mmrJaccardThreshold: 0.85,
|
|
75
|
-
mmrPenaltyFactor: 0.5,
|
|
76
|
-
phase2Multiplier: 2,
|
|
77
|
-
llmMaxEval: 15,
|
|
78
|
-
refineStrategy: "loose",
|
|
79
|
-
},
|
|
80
67
|
logging: {
|
|
81
68
|
logEnabled: true,
|
|
82
69
|
logLevel: "INFO",
|
|
@@ -88,9 +75,6 @@ const DEFAULTS: OmemPluginConfig = {
|
|
|
88
75
|
web: {
|
|
89
76
|
enabled: true,
|
|
90
77
|
},
|
|
91
|
-
profile: {
|
|
92
|
-
ttlMs: 300000,
|
|
93
|
-
},
|
|
94
78
|
};
|
|
95
79
|
|
|
96
80
|
// ── Flat-to-nested migration ─────────────────────────────────────────
|
|
@@ -105,8 +89,6 @@ interface FlatConfig {
|
|
|
105
89
|
maxContentLength?: number;
|
|
106
90
|
autoCaptureThreshold?: number;
|
|
107
91
|
ingestMode?: "smart" | "raw";
|
|
108
|
-
similarityThreshold?: number;
|
|
109
|
-
maxRecallResults?: number;
|
|
110
92
|
toastDelayMs?: number;
|
|
111
93
|
logEnabled?: boolean;
|
|
112
94
|
logLevel?: "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
@@ -119,7 +101,7 @@ function isFlatConfig(cfg: Record<string, unknown>): boolean {
|
|
|
119
101
|
return "apiUrl" in cfg && !("connection" in cfg);
|
|
120
102
|
}
|
|
121
103
|
|
|
122
|
-
function migrateFlatToNested(flat: FlatConfig):
|
|
104
|
+
function migrateFlatToNested(flat: FlatConfig): CerebroPluginConfig {
|
|
123
105
|
return {
|
|
124
106
|
connection: {
|
|
125
107
|
apiUrl: flat.apiUrl ?? DEFAULTS.connection.apiUrl,
|
|
@@ -131,21 +113,11 @@ function migrateFlatToNested(flat: FlatConfig): OmemPluginConfig {
|
|
|
131
113
|
maxContentChars: flat.maxContentChars ?? DEFAULTS.content.maxContentChars,
|
|
132
114
|
maxContentLength: flat.maxContentLength ?? DEFAULTS.content.maxContentLength,
|
|
133
115
|
},
|
|
116
|
+
injection: { ...DEFAULTS.injection },
|
|
134
117
|
ingest: {
|
|
135
118
|
autoCaptureThreshold: flat.autoCaptureThreshold ?? DEFAULTS.ingest.autoCaptureThreshold,
|
|
136
119
|
ingestMode: flat.ingestMode ?? DEFAULTS.ingest.ingestMode,
|
|
137
120
|
},
|
|
138
|
-
recall: {
|
|
139
|
-
similarityThreshold: flat.similarityThreshold ?? DEFAULTS.recall.similarityThreshold,
|
|
140
|
-
maxRecallResults: flat.maxRecallResults ?? DEFAULTS.recall.maxRecallResults,
|
|
141
|
-
fetchMultiplier: DEFAULTS.recall.fetchMultiplier,
|
|
142
|
-
topkCapMultiplier: DEFAULTS.recall.topkCapMultiplier,
|
|
143
|
-
mmrJaccardThreshold: DEFAULTS.recall.mmrJaccardThreshold,
|
|
144
|
-
mmrPenaltyFactor: DEFAULTS.recall.mmrPenaltyFactor,
|
|
145
|
-
phase2Multiplier: DEFAULTS.recall.phase2Multiplier,
|
|
146
|
-
llmMaxEval: DEFAULTS.recall.llmMaxEval,
|
|
147
|
-
refineStrategy: DEFAULTS.recall.refineStrategy,
|
|
148
|
-
},
|
|
149
121
|
logging: {
|
|
150
122
|
logEnabled: flat.logEnabled ?? DEFAULTS.logging.logEnabled,
|
|
151
123
|
logLevel: flat.logLevel ?? DEFAULTS.logging.logLevel,
|
|
@@ -162,17 +134,16 @@ function migrateFlatToNested(flat: FlatConfig): OmemPluginConfig {
|
|
|
162
134
|
type IngestMode = "smart" | "raw";
|
|
163
135
|
const INGEST_MODES: ReadonlySet<string> = new Set<IngestMode>(["smart", "raw"]);
|
|
164
136
|
|
|
165
|
-
function deepMerge(base:
|
|
166
|
-
const result:
|
|
137
|
+
function deepMerge(base: CerebroPluginConfig, overrides: Partial<CerebroPluginConfig>): CerebroPluginConfig {
|
|
138
|
+
const result: CerebroPluginConfig = {
|
|
167
139
|
connection: { ...base.connection, ...overrides.connection },
|
|
168
140
|
content: { ...base.content, ...overrides.content },
|
|
141
|
+
injection: { ...base.injection, ...overrides.injection },
|
|
169
142
|
ingest: { ...base.ingest, ...overrides.ingest },
|
|
170
|
-
recall: { ...base.recall, ...overrides.recall },
|
|
171
143
|
logging: { ...base.logging, ...overrides.logging },
|
|
172
144
|
ui: { ...base.ui, ...overrides.ui },
|
|
173
145
|
};
|
|
174
146
|
result.web = { ...base.web!, ...overrides.web };
|
|
175
|
-
result.profile = { ...base.profile!, ...overrides.profile };
|
|
176
147
|
if (overrides.agentMemoryPolicy) result.agentMemoryPolicy = overrides.agentMemoryPolicy;
|
|
177
148
|
if (overrides.defaultPolicy) result.defaultPolicy = overrides.defaultPolicy;
|
|
178
149
|
return result;
|
|
@@ -218,8 +189,8 @@ function configLog(message: string, fields?: Record<string, unknown>, level: str
|
|
|
218
189
|
}
|
|
219
190
|
}
|
|
220
191
|
|
|
221
|
-
export function loadPluginConfig(overrides?: Partial<
|
|
222
|
-
let config:
|
|
192
|
+
export function loadPluginConfig(overrides?: Partial<CerebroPluginConfig>): CerebroPluginConfig {
|
|
193
|
+
let config: CerebroPluginConfig = structuredClone(DEFAULTS);
|
|
223
194
|
|
|
224
195
|
// Try loading from config file
|
|
225
196
|
try {
|
|
@@ -227,7 +198,7 @@ export function loadPluginConfig(overrides?: Partial<OmemPluginConfig>): OmemPlu
|
|
|
227
198
|
const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
|
|
228
199
|
|
|
229
200
|
// Auto-migrate flat format
|
|
230
|
-
const parsed:
|
|
201
|
+
const parsed: CerebroPluginConfig = isFlatConfig(raw) ? migrateFlatToNested(raw as FlatConfig) : raw as unknown as CerebroPluginConfig;
|
|
231
202
|
|
|
232
203
|
// Merge nested groups with defaults for safety
|
|
233
204
|
config = deepMerge(config, parsed);
|
|
@@ -252,12 +223,6 @@ export function loadPluginConfig(overrides?: Partial<OmemPluginConfig>): OmemPlu
|
|
|
252
223
|
if (INGEST_MODES.has(process.env.OMEM_INGEST_MODE ?? "")) {
|
|
253
224
|
config.ingest.ingestMode = process.env.OMEM_INGEST_MODE as IngestMode;
|
|
254
225
|
}
|
|
255
|
-
if (process.env.OMEM_SIMILARITY_THRESHOLD) {
|
|
256
|
-
config.recall.similarityThreshold = parseFloat(process.env.OMEM_SIMILARITY_THRESHOLD) || DEFAULTS.recall.similarityThreshold;
|
|
257
|
-
}
|
|
258
|
-
if (process.env.OMEM_MAX_RECALL_RESULTS) {
|
|
259
|
-
config.recall.maxRecallResults = parseInt(process.env.OMEM_MAX_RECALL_RESULTS, 10) || DEFAULTS.recall.maxRecallResults;
|
|
260
|
-
}
|
|
261
226
|
|
|
262
227
|
if (process.env.OMEM_WEB_ENABLED === "false" || process.env.OMEM_WEB_ENABLED === "0") {
|
|
263
228
|
config.web = { ...config.web!, enabled: false };
|
|
@@ -280,7 +245,7 @@ export type AgentPolicy = "none" | "readonly" | "readwrite";
|
|
|
280
245
|
|
|
281
246
|
export function resolveAgentPolicy(
|
|
282
247
|
agentName: string,
|
|
283
|
-
config: Partial<
|
|
248
|
+
config: Partial<CerebroPluginConfig>,
|
|
284
249
|
): AgentPolicy {
|
|
285
250
|
const policies = config.agentMemoryPolicy;
|
|
286
251
|
if (policies) {
|
package/src/hooks.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Model, UserMessage, Part } from "@opencode-ai/sdk";
|
|
2
2
|
import type { CerebroClient, SearchResult } from "./client.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type CerebroPluginConfig, DEFAULTS, resolveAgentPolicy } from "./config.js";
|
|
4
4
|
import { logDebug, logInfo, logError as logErr } from "./logger.js";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
6
|
|
|
@@ -111,15 +111,23 @@ async function detectProjectName(rootPath: string): Promise<string | undefined>
|
|
|
111
111
|
return result;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export function showToast(tui: any, title: string, message: string, variant: string = "info", delayMs
|
|
114
|
+
export function showToast(tui: any, title: string, message: string, variant: string = "info", delayMs?: number) {
|
|
115
115
|
if (!tui) return;
|
|
116
|
+
const effectiveDelay = delayMs ?? DEFAULTS.ui.toastDelayMs;
|
|
116
117
|
setTimeout(() => {
|
|
117
118
|
try {
|
|
118
119
|
tui.showToast({ body: { title, message, variant, duration: 5000 } });
|
|
119
120
|
} catch (err) {
|
|
120
121
|
logErr("showToast failed", { error: String(err) });
|
|
121
122
|
}
|
|
122
|
-
},
|
|
123
|
+
}, effectiveDelay);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createToast(config: Partial<CerebroPluginConfig>) {
|
|
127
|
+
const defaultDelay = config.ui?.toastDelayMs ?? DEFAULTS.ui.toastDelayMs;
|
|
128
|
+
return (tui: any, title: string, message: string, variant: string = "info", delayMs?: number) => {
|
|
129
|
+
showToast(tui, title, message, variant, delayMs ?? defaultDelay);
|
|
130
|
+
};
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
const SYSTEM_INJECTION_PATTERNS: RegExp[] = [
|
|
@@ -211,7 +219,7 @@ const FETCH_POLICY = [
|
|
|
211
219
|
"</cerebro-fetch-policy>",
|
|
212
220
|
].join("\n");
|
|
213
221
|
|
|
214
|
-
const
|
|
222
|
+
const MAX_INJECTION_CHARS_FALLBACK = DEFAULTS.content.maxContentChars;
|
|
215
223
|
|
|
216
224
|
interface InjectionResult {
|
|
217
225
|
text: string;
|
|
@@ -224,9 +232,14 @@ export async function buildMemoryInjection(
|
|
|
224
232
|
client: CerebroClient,
|
|
225
233
|
projectPath: string | undefined,
|
|
226
234
|
query: string,
|
|
227
|
-
config: Partial<
|
|
235
|
+
config: Partial<CerebroPluginConfig>,
|
|
228
236
|
): Promise<InjectionResult> {
|
|
229
|
-
const maxChars = config.content?.
|
|
237
|
+
const maxChars = config.content?.maxContentChars ?? MAX_INJECTION_CHARS_FALLBACK;
|
|
238
|
+
const ic = config.injection ?? DEFAULTS.injection;
|
|
239
|
+
const recentCount = ic.recentCount || DEFAULTS.injection.recentCount;
|
|
240
|
+
const searchCount = ic.searchCount || DEFAULTS.injection.searchCount;
|
|
241
|
+
const recentTruncate = ic.recentTruncateChars || 0; // 0 = 不截断
|
|
242
|
+
const searchTruncate = ic.searchTruncateChars || 0; // 0 = 不截断
|
|
230
243
|
|
|
231
244
|
const [profile, projectMemories, searchResults] = await Promise.all([
|
|
232
245
|
Promise.race([
|
|
@@ -234,12 +247,12 @@ export async function buildMemoryInjection(
|
|
|
234
247
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), 1000)),
|
|
235
248
|
]).catch(() => null),
|
|
236
249
|
Promise.race([
|
|
237
|
-
client.listRecent(
|
|
250
|
+
client.listRecent(recentCount, projectPath),
|
|
238
251
|
new Promise<never[]>((resolve) => setTimeout(() => resolve([]), 1000)),
|
|
239
252
|
]).catch(() => []),
|
|
240
253
|
query
|
|
241
254
|
? Promise.race([
|
|
242
|
-
client.searchMemories(query,
|
|
255
|
+
client.searchMemories(query, searchCount, undefined, undefined, projectPath),
|
|
243
256
|
new Promise<never[]>((resolve) => setTimeout(() => resolve([]), 1500)),
|
|
244
257
|
]).catch(() => [])
|
|
245
258
|
: Promise.resolve([]),
|
|
@@ -259,7 +272,7 @@ export async function buildMemoryInjection(
|
|
|
259
272
|
for (const m of projectMemories) {
|
|
260
273
|
seenIds.add(m.id);
|
|
261
274
|
const age = formatRelativeAge(m.updated_at || m.created_at) || "unknown";
|
|
262
|
-
const content = truncate(m.content,
|
|
275
|
+
const content = recentTruncate > 0 ? truncate(m.content, recentTruncate) : m.content;
|
|
263
276
|
sections.push(`- (${age}) ${content}`);
|
|
264
277
|
}
|
|
265
278
|
sections.push("");
|
|
@@ -270,7 +283,7 @@ export async function buildMemoryInjection(
|
|
|
270
283
|
sections.push("## Relevant Memories");
|
|
271
284
|
for (const r of dedupedResults) {
|
|
272
285
|
const age = formatRelativeAge(r.memory.created_at) || "unknown";
|
|
273
|
-
const content = truncate(r.memory.content,
|
|
286
|
+
const content = searchTruncate > 0 ? truncate(r.memory.content, searchTruncate) : r.memory.content;
|
|
274
287
|
sections.push(`- (${age}) ${content}`);
|
|
275
288
|
}
|
|
276
289
|
sections.push("");
|
|
@@ -298,7 +311,7 @@ export function chatMessageRecallHook(
|
|
|
298
311
|
client: CerebroClient,
|
|
299
312
|
_containerTags: string[],
|
|
300
313
|
tui: any,
|
|
301
|
-
config: Partial<
|
|
314
|
+
config: Partial<CerebroPluginConfig> = {},
|
|
302
315
|
getAgentName?: () => string,
|
|
303
316
|
directory?: string,
|
|
304
317
|
) {
|
|
@@ -435,7 +448,7 @@ export function createCerebroCompactionPrompt(
|
|
|
435
448
|
return sections.join("\n");
|
|
436
449
|
}
|
|
437
450
|
|
|
438
|
-
export function compactingHook(client: CerebroClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any, config: Partial<
|
|
451
|
+
export function compactingHook(client: CerebroClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any, config: Partial<CerebroPluginConfig> = {}, agentId?: string, directory?: string) {
|
|
439
452
|
const effectiveAgentId = agentId || process.env.OMEM_AGENT_ID || "opencode";
|
|
440
453
|
return async (
|
|
441
454
|
input: { sessionID?: string },
|
|
@@ -568,7 +581,7 @@ export function autocontinueHook(
|
|
|
568
581
|
isAutoStoreEnabled?: (sessionId: string | undefined) => boolean,
|
|
569
582
|
getMainSessionId?: () => string | undefined,
|
|
570
583
|
sdkClient?: any,
|
|
571
|
-
config: Partial<
|
|
584
|
+
config: Partial<CerebroPluginConfig> = {},
|
|
572
585
|
agentId?: string,
|
|
573
586
|
directory?: string,
|
|
574
587
|
) {
|
|
@@ -683,7 +696,7 @@ export function sessionIdleHook(
|
|
|
683
696
|
getMainSessionId?: () => string | undefined,
|
|
684
697
|
isAutoStoreEnabled?: (sessionId: string | undefined) => boolean,
|
|
685
698
|
agentId?: string,
|
|
686
|
-
config: Partial<
|
|
699
|
+
config: Partial<CerebroPluginConfig> = {},
|
|
687
700
|
onAgentResolved?: (name: string) => void,
|
|
688
701
|
directory?: string,
|
|
689
702
|
) {
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import type { Server } from "node:http";
|
|
7
7
|
import { CerebroClient } from "./client.js";
|
|
8
|
-
import { chatMessageRecallHook, autocontinueHook, compactingHook, sessionIdleHook,
|
|
8
|
+
import { chatMessageRecallHook, autocontinueHook, compactingHook, sessionIdleHook, createToast, sessionMessages, firstMessages } from "./hooks.js";
|
|
9
9
|
import { detectSaveKeyword, detectRecallKeyword, KEYWORD_NUDGE, RECALL_NUDGE } from "./keywords.js";
|
|
10
10
|
import { getUserTag, getProjectTag } from "./tags.js";
|
|
11
11
|
import { buildTools } from "./tools.js";
|
|
@@ -64,33 +64,30 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
64
64
|
} catch {}
|
|
65
65
|
|
|
66
66
|
const config = loadPluginConfig(overrides as any);
|
|
67
|
+
const toast = createToast(config);
|
|
67
68
|
|
|
68
69
|
const cerebroClient = new CerebroClient(config.connection.apiUrl, config.connection.apiKey, config);
|
|
69
70
|
|
|
70
|
-
// 启动时检测连接状态
|
|
71
71
|
try {
|
|
72
72
|
await cerebroClient.getStats();
|
|
73
|
-
hooksShowToast(tui, "🧠 Cerebro · Connected", `Version v${pluginVersion}`, "success", 6000);
|
|
74
73
|
logInfo(`Connected to ${config.connection.apiUrl}`);
|
|
75
74
|
} catch (err) {
|
|
76
75
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
77
76
|
logError(`Connection failed: ${errMsg}`);
|
|
78
77
|
if (errMsg.includes("[cerebro]")) {
|
|
79
78
|
const cleanMsg = errMsg.replace(/^\[cerebro\]\s*/, "");
|
|
80
|
-
|
|
79
|
+
toast(
|
|
81
80
|
tui,
|
|
82
81
|
`🧠 Cerebro v${pluginVersion} · Server Error`,
|
|
83
82
|
cleanMsg.substring(0, 150),
|
|
84
|
-
"error"
|
|
85
|
-
8000
|
|
83
|
+
"error"
|
|
86
84
|
);
|
|
87
85
|
} else {
|
|
88
|
-
|
|
86
|
+
toast(
|
|
89
87
|
tui,
|
|
90
88
|
`🧠 Cerebro v${pluginVersion} · Connection Failed`,
|
|
91
89
|
`Unable to reach ${config.connection.apiUrl}`,
|
|
92
|
-
"error"
|
|
93
|
-
8000
|
|
90
|
+
"error"
|
|
94
91
|
);
|
|
95
92
|
}
|
|
96
93
|
}
|
|
@@ -108,6 +105,7 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
108
105
|
|
|
109
106
|
let webServer: Server | null = null;
|
|
110
107
|
const webEnabled = config.web?.enabled !== false;
|
|
108
|
+
let webPort: number | undefined;
|
|
111
109
|
if (webEnabled) {
|
|
112
110
|
try {
|
|
113
111
|
webServer = await startWebServer({
|
|
@@ -116,15 +114,20 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
116
114
|
});
|
|
117
115
|
if (webServer) {
|
|
118
116
|
const addr = webServer.address();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
logInfo(`Web UI available at http://localhost:${actualPort}`);
|
|
117
|
+
webPort = typeof addr === "object" && addr ? addr.port : config.web?.port || 5212;
|
|
118
|
+
logInfo(`Web UI available at http://localhost:${webPort}`);
|
|
122
119
|
}
|
|
123
120
|
} catch (err) {
|
|
124
121
|
logError(`Web server start failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
125
122
|
}
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
if (webPort) {
|
|
126
|
+
toast(tui, `🧠 Cerebro Connected · v${pluginVersion}`, `\n🌐 Open in browser http://localhost:${webPort}`, "success");
|
|
127
|
+
} else {
|
|
128
|
+
toast(tui, "🧠 Connected", `v${pluginVersion}`, "success");
|
|
129
|
+
}
|
|
130
|
+
|
|
128
131
|
const shutdown = async () => {
|
|
129
132
|
try {
|
|
130
133
|
if (webServer) {
|