@mingxy/cerebro 1.8.2 → 1.9.0

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/index.ts CHANGED
@@ -1,151 +1,151 @@
1
- import type { Plugin } from "@opencode-ai/plugin";
2
- import { readFileSync, writeFileSync } from "node:fs";
3
- import { join, dirname } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import { fileURLToPath } from "node:url";
6
- import { OmemClient } from "./client.js";
7
- import { autoRecallHook, compactingHook, keywordDetectionHook, sessionIdleHook } from "./hooks.js";
8
- import { getUserTag, getProjectTag } from "./tags.js";
9
- import { buildTools } from "./tools.js";
10
- import { logInfo, logError } from "./logger.js";
11
- import { loadPluginConfig } from "./config.js";
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
-
16
- let pluginVersion = "unknown";
17
- try {
18
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
19
- if (pkg?.version && typeof pkg.version === "string") {
20
- pluginVersion = pkg.version;
21
- }
22
- } catch {}
23
-
24
- // Per-session auto-store toggle: sessionId → enabled (default: true = auto-store on)
25
- const autoStoreSessions = new Map<string, boolean>();
26
-
27
- function getStateFilePath(sessionId: string): string {
28
- return join(tmpdir(), `cerebro_autostore_${sessionId}.json`);
29
- }
30
-
31
- export function isAutoStoreEnabled(sessionId: string | undefined): boolean {
32
- if (!sessionId) return true;
33
- return autoStoreSessions.get(sessionId) ?? true;
34
- }
35
-
36
- export function setAutoStoreEnabled(sessionId: string, enabled: boolean): void {
37
- autoStoreSessions.set(sessionId, enabled);
38
- try {
39
- writeFileSync(getStateFilePath(sessionId), JSON.stringify({ enabled }));
40
- } catch {}
41
- }
42
-
43
- (globalThis as any).__cerebro_autoStoreMap = autoStoreSessions;
44
-
45
- function showToast(tui: any, title: string, message?: string, variant: string = "info", duration: number = 5000) {
46
- if (!tui) return;
47
- setTimeout(() => {
48
- try {
49
- const body: any = { variant, duration };
50
- if (message) {
51
- body.title = title;
52
- body.message = message;
53
- } else {
54
- body.message = title;
55
- }
56
- tui.showToast({ body });
57
- } catch (err) {
58
- console.error("[cerebro] showToast failed:", err);
59
- }
60
- }, 3000);
61
- }
62
-
63
- const OmemPlugin: Plugin = async (input) => {
64
- const { directory, client } = input;
65
- // Proxy: dynamically resolve client.tui on each access so toast works
66
- // even if client.tui isn't ready yet at plugin init time
67
- const tui = new Proxy({} as any, {
68
- get(_, prop) {
69
- return (client as any)?.tui?.[prop];
70
- },
71
- });
72
-
73
- // Load overrides from opencode.json plugin_config
74
- let overrides: Record<string, unknown> = {};
75
- try {
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"];
78
- if (pc) overrides = pc;
79
- } catch {}
80
-
81
- const config = loadPluginConfig(overrides as any);
82
-
83
- const omemClient = new OmemClient(config.apiUrl, config.apiKey, config);
84
-
85
- // 启动时检测连接状态
86
- try {
87
- await omemClient.getStats();
88
- showToast(tui, "🧠 Cerebro · Connected", `Version v${pluginVersion}`, "success", 6000);
89
- logInfo(`Connected to ${config.apiUrl}`);
90
- } catch (err) {
91
- const errMsg = err instanceof Error ? err.message : String(err);
92
- logError(`Connection failed: ${errMsg}`);
93
- if (errMsg.includes("[omem]")) {
94
- const cleanMsg = errMsg.replace(/^\[omem\]\s*/, "");
95
- showToast(
96
- tui,
97
- `🧠 Cerebro v${pluginVersion} · Server Error`,
98
- cleanMsg.substring(0, 150),
99
- "error",
100
- 8000
101
- );
102
- } else {
103
- showToast(
104
- tui,
105
- `🧠 Cerebro v${pluginVersion} · Connection Failed`,
106
- `Unable to reach ${config.apiUrl}`,
107
- "error",
108
- 8000
109
- );
110
- }
111
- }
112
-
113
- const email = process.env.GIT_AUTHOR_EMAIL || process.env.USER || "unknown";
114
- const cwd = directory || process.cwd();
115
- const containerTags = [getUserTag(email), getProjectTag(cwd)];
116
- const agentId = process.env.OMEM_AGENT_ID || "opencode";
117
-
118
- let currentSessionId: string | undefined;
119
-
120
- const recallHook = autoRecallHook(omemClient, containerTags, tui, config);
121
-
122
- return {
123
- config: async (cfg: any) => {
124
- cfg.command ??= {};
125
- cfg.command["memory-toggle"] = {
126
- template: "Use the memory_toggle tool with state='$ARGUMENTS' to toggle Cerebro auto-store for this session. You MUST call the memory_toggle tool, do not just acknowledge.",
127
- description: "Toggle Cerebro auto-store ON or OFF for current session",
128
- };
129
- },
130
- "experimental.chat.system.transform": async (input: any, output: any) => {
131
- if (input.sessionID) currentSessionId = input.sessionID;
132
- return recallHook(input, output);
133
- },
134
- "chat.message": keywordDetectionHook(omemClient, containerTags, config.autoCaptureThreshold, tui, config.ingestMode),
135
- "experimental.session.compacting": compactingHook(omemClient, containerTags, tui, config.ingestMode, isAutoStoreEnabled),
136
- tool: buildTools(omemClient, containerTags, { agentId, getSessionId: () => currentSessionId }),
137
- event: sessionIdleHook(omemClient, containerTags, tui, client, config.ingestMode, config.autoCaptureThreshold, () => currentSessionId, isAutoStoreEnabled),
138
- "shell.env": async (_input: any, output: any) => {
139
- if (directory) {
140
- output.env.OMEM_PROJECT_DIR = directory;
141
- }
142
- },
143
- };
144
- };
145
-
146
- export { OmemPlugin };
147
-
148
- export default {
149
- id: "ourmem",
150
- server: OmemPlugin,
151
- };
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { fileURLToPath } from "node:url";
6
+ import { OmemClient } from "./client.js";
7
+ import { autoRecallHook, compactingHook, keywordDetectionHook, sessionIdleHook } from "./hooks.js";
8
+ import { getUserTag, getProjectTag } from "./tags.js";
9
+ import { buildTools } from "./tools.js";
10
+ import { logInfo, logError } from "./logger.js";
11
+ import { loadPluginConfig } from "./config.js";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ let pluginVersion = "unknown";
17
+ try {
18
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
19
+ if (pkg?.version && typeof pkg.version === "string") {
20
+ pluginVersion = pkg.version;
21
+ }
22
+ } catch {}
23
+
24
+ // Per-session auto-store toggle: sessionId → enabled (default: true = auto-store on)
25
+ const autoStoreSessions = new Map<string, boolean>();
26
+
27
+ function getStateFilePath(sessionId: string): string {
28
+ return join(tmpdir(), `cerebro_autostore_${sessionId}.json`);
29
+ }
30
+
31
+ export function isAutoStoreEnabled(sessionId: string | undefined): boolean {
32
+ if (!sessionId) return true;
33
+ return autoStoreSessions.get(sessionId) ?? true;
34
+ }
35
+
36
+ export function setAutoStoreEnabled(sessionId: string, enabled: boolean): void {
37
+ autoStoreSessions.set(sessionId, enabled);
38
+ try {
39
+ writeFileSync(getStateFilePath(sessionId), JSON.stringify({ enabled }));
40
+ } catch {}
41
+ }
42
+
43
+ (globalThis as any).__cerebro_autoStoreMap = autoStoreSessions;
44
+
45
+ function showToast(tui: any, title: string, message?: string, variant: string = "info", duration: number = 5000) {
46
+ if (!tui) return;
47
+ setTimeout(() => {
48
+ try {
49
+ const body: any = { variant, duration };
50
+ if (message) {
51
+ body.title = title;
52
+ body.message = message;
53
+ } else {
54
+ body.message = title;
55
+ }
56
+ tui.showToast({ body });
57
+ } catch (err) {
58
+ console.error("[cerebro] showToast failed:", err);
59
+ }
60
+ }, 3000);
61
+ }
62
+
63
+ const OmemPlugin: Plugin = async (input) => {
64
+ const { directory, client } = input;
65
+ // Proxy: dynamically resolve client.tui on each access so toast works
66
+ // even if client.tui isn't ready yet at plugin init time
67
+ const tui = new Proxy({} as any, {
68
+ get(_, prop) {
69
+ return (client as any)?.tui?.[prop];
70
+ },
71
+ });
72
+
73
+ // Load overrides from opencode.json plugin_config
74
+ let overrides: Record<string, unknown> = {};
75
+ try {
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"];
78
+ if (pc) overrides = pc;
79
+ } catch {}
80
+
81
+ const config = loadPluginConfig(overrides as any);
82
+
83
+ const omemClient = new OmemClient(config.apiUrl, config.apiKey, config);
84
+
85
+ // 启动时检测连接状态
86
+ try {
87
+ await omemClient.getStats();
88
+ showToast(tui, "🧠 Cerebro · Connected", `Version v${pluginVersion}`, "success", 6000);
89
+ logInfo(`Connected to ${config.apiUrl}`);
90
+ } catch (err) {
91
+ const errMsg = err instanceof Error ? err.message : String(err);
92
+ logError(`Connection failed: ${errMsg}`);
93
+ if (errMsg.includes("[omem]")) {
94
+ const cleanMsg = errMsg.replace(/^\[omem\]\s*/, "");
95
+ showToast(
96
+ tui,
97
+ `🧠 Cerebro v${pluginVersion} · Server Error`,
98
+ cleanMsg.substring(0, 150),
99
+ "error",
100
+ 8000
101
+ );
102
+ } else {
103
+ showToast(
104
+ tui,
105
+ `🧠 Cerebro v${pluginVersion} · Connection Failed`,
106
+ `Unable to reach ${config.apiUrl}`,
107
+ "error",
108
+ 8000
109
+ );
110
+ }
111
+ }
112
+
113
+ const email = process.env.GIT_AUTHOR_EMAIL || process.env.USER || "unknown";
114
+ const cwd = directory || process.cwd();
115
+ const containerTags = [getUserTag(email), getProjectTag(cwd)];
116
+ const agentId = process.env.OMEM_AGENT_ID || "opencode";
117
+
118
+ let currentSessionId: string | undefined;
119
+
120
+ const recallHook = autoRecallHook(omemClient, containerTags, tui, config);
121
+
122
+ return {
123
+ config: async (cfg: any) => {
124
+ cfg.command ??= {};
125
+ cfg.command["memory-toggle"] = {
126
+ template: "Use the memory_toggle tool with state='$ARGUMENTS' to toggle Cerebro auto-store for this session. You MUST call the memory_toggle tool, do not just acknowledge.",
127
+ description: "Toggle Cerebro auto-store ON or OFF for current session",
128
+ };
129
+ },
130
+ "experimental.chat.system.transform": async (input: any, output: any) => {
131
+ if (input.sessionID) currentSessionId = input.sessionID;
132
+ return recallHook(input, output);
133
+ },
134
+ "chat.message": keywordDetectionHook(omemClient, containerTags, config.autoCaptureThreshold, tui, config.ingestMode),
135
+ "experimental.session.compacting": compactingHook(omemClient, containerTags, tui, config.ingestMode, isAutoStoreEnabled, () => currentSessionId),
136
+ tool: buildTools(omemClient, containerTags, { agentId, getSessionId: () => currentSessionId }),
137
+ event: sessionIdleHook(omemClient, containerTags, tui, client, config.ingestMode, config.autoCaptureThreshold, () => currentSessionId, isAutoStoreEnabled, agentId),
138
+ "shell.env": async (_input: any, output: any) => {
139
+ if (directory) {
140
+ output.env.OMEM_PROJECT_DIR = directory;
141
+ }
142
+ },
143
+ };
144
+ };
145
+
146
+ export { OmemPlugin };
147
+
148
+ export default {
149
+ id: "ourmem",
150
+ server: OmemPlugin,
151
+ };