opencode-mem 1.0.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/README.md +588 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +258 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +618 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +15 -0
- package/dist/services/api-handlers.d.ts +102 -0
- package/dist/services/api-handlers.d.ts.map +1 -0
- package/dist/services/api-handlers.js +494 -0
- package/dist/services/auto-capture.d.ts +32 -0
- package/dist/services/auto-capture.d.ts.map +1 -0
- package/dist/services/auto-capture.js +451 -0
- package/dist/services/cleanup-service.d.ts +20 -0
- package/dist/services/cleanup-service.d.ts.map +1 -0
- package/dist/services/cleanup-service.js +88 -0
- package/dist/services/client.d.ts +104 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +251 -0
- package/dist/services/compaction.d.ts +92 -0
- package/dist/services/compaction.d.ts.map +1 -0
- package/dist/services/compaction.js +421 -0
- package/dist/services/context.d.ts +17 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +41 -0
- package/dist/services/deduplication-service.d.ts +30 -0
- package/dist/services/deduplication-service.d.ts.map +1 -0
- package/dist/services/deduplication-service.js +131 -0
- package/dist/services/embedding.d.ts +10 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +77 -0
- package/dist/services/jsonc.d.ts +7 -0
- package/dist/services/jsonc.d.ts.map +1 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/logger.d.ts +2 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +16 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.d.ts.map +1 -0
- package/dist/services/migration-service.js +258 -0
- package/dist/services/privacy.d.ts +4 -0
- package/dist/services/privacy.d.ts.map +1 -0
- package/dist/services/privacy.js +10 -0
- package/dist/services/sqlite/connection-manager.d.ts +10 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
- package/dist/services/sqlite/connection-manager.js +45 -0
- package/dist/services/sqlite/shard-manager.d.ts +20 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
- package/dist/services/sqlite/shard-manager.js +221 -0
- package/dist/services/sqlite/types.d.ts +39 -0
- package/dist/services/sqlite/types.d.ts.map +1 -0
- package/dist/services/sqlite/types.js +1 -0
- package/dist/services/sqlite/vector-search.d.ts +18 -0
- package/dist/services/sqlite/vector-search.d.ts.map +1 -0
- package/dist/services/sqlite/vector-search.js +129 -0
- package/dist/services/sqlite-client.d.ts +116 -0
- package/dist/services/sqlite-client.d.ts.map +1 -0
- package/dist/services/sqlite-client.js +284 -0
- package/dist/services/tags.d.ts +20 -0
- package/dist/services/tags.d.ts.map +1 -0
- package/dist/services/tags.js +76 -0
- package/dist/services/web-server-lock.d.ts +12 -0
- package/dist/services/web-server-lock.d.ts.map +1 -0
- package/dist/services/web-server-lock.js +157 -0
- package/dist/services/web-server-worker.d.ts +2 -0
- package/dist/services/web-server-worker.d.ts.map +1 -0
- package/dist/services/web-server-worker.js +221 -0
- package/dist/services/web-server.d.ts +22 -0
- package/dist/services/web-server.d.ts.map +1 -0
- package/dist/services/web-server.js +134 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +691 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +14 -0
- package/dist/web/index.html +202 -0
- package/dist/web/styles.css +851 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { memoryClient } from "./services/client.js";
|
|
3
|
+
import { formatContextForPrompt } from "./services/context.js";
|
|
4
|
+
import { getTags } from "./services/tags.js";
|
|
5
|
+
import { stripPrivateContent, isFullyPrivate } from "./services/privacy.js";
|
|
6
|
+
import { AutoCaptureService, performAutoCapture } from "./services/auto-capture.js";
|
|
7
|
+
import { startWebServer, WebServer } from "./services/web-server.js";
|
|
8
|
+
import { isConfigured, CONFIG } from "./config.js";
|
|
9
|
+
import { log } from "./services/logger.js";
|
|
10
|
+
const CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
11
|
+
const INLINE_CODE_PATTERN = /`[^`]+`/g;
|
|
12
|
+
const MEMORY_KEYWORD_PATTERN = new RegExp(`\\b(${CONFIG.keywordPatterns.join("|")})\\b`, "i");
|
|
13
|
+
const MEMORY_NUDGE_MESSAGE = `[MEMORY TRIGGER DETECTED]
|
|
14
|
+
The user wants you to remember something. You MUST use the \`memory\` tool with \`mode: "add"\` to save this information.
|
|
15
|
+
|
|
16
|
+
Extract the key information the user wants remembered and save it as a concise, searchable memory.
|
|
17
|
+
- Use \`scope: "project"\` for project-specific preferences (e.g., "run lint with tests")
|
|
18
|
+
- Use \`scope: "user"\` for cross-project preferences (e.g., "prefers concise responses")
|
|
19
|
+
- Choose an appropriate \`type\`: "preference", "project-config", "learned-pattern", etc.
|
|
20
|
+
|
|
21
|
+
DO NOT skip this step. The user explicitly asked you to remember.`;
|
|
22
|
+
function removeCodeBlocks(text) {
|
|
23
|
+
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
|
|
24
|
+
}
|
|
25
|
+
function detectMemoryKeyword(text) {
|
|
26
|
+
const textWithoutCode = removeCodeBlocks(text);
|
|
27
|
+
return MEMORY_KEYWORD_PATTERN.test(textWithoutCode);
|
|
28
|
+
}
|
|
29
|
+
export const OpenCodeMemPlugin = async (ctx) => {
|
|
30
|
+
const { directory } = ctx;
|
|
31
|
+
const tags = getTags(directory);
|
|
32
|
+
const injectedSessions = new Set();
|
|
33
|
+
const autoCaptureService = new AutoCaptureService();
|
|
34
|
+
let webServer = null;
|
|
35
|
+
log("Plugin loaded", {
|
|
36
|
+
directory,
|
|
37
|
+
tags,
|
|
38
|
+
configured: isConfigured(),
|
|
39
|
+
autoCaptureEnabled: autoCaptureService.isEnabled(),
|
|
40
|
+
});
|
|
41
|
+
if (!isConfigured()) {
|
|
42
|
+
log("Plugin disabled - memory system not configured");
|
|
43
|
+
}
|
|
44
|
+
if (CONFIG.webServerEnabled) {
|
|
45
|
+
startWebServer({
|
|
46
|
+
port: CONFIG.webServerPort,
|
|
47
|
+
host: CONFIG.webServerHost,
|
|
48
|
+
enabled: CONFIG.webServerEnabled,
|
|
49
|
+
})
|
|
50
|
+
.then((server) => {
|
|
51
|
+
webServer = server;
|
|
52
|
+
const url = webServer.getUrl();
|
|
53
|
+
if (webServer.isServerOwner()) {
|
|
54
|
+
log("Web server started (owner)", { url });
|
|
55
|
+
if (ctx.client?.tui) {
|
|
56
|
+
ctx.client.tui
|
|
57
|
+
.showToast({
|
|
58
|
+
body: {
|
|
59
|
+
title: "Memory Explorer",
|
|
60
|
+
message: `Web UI started at ${url}`,
|
|
61
|
+
variant: "success",
|
|
62
|
+
duration: 5000,
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
.catch(() => { });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
log("Web server already running (joined)", { url });
|
|
70
|
+
if (ctx.client?.tui) {
|
|
71
|
+
ctx.client.tui
|
|
72
|
+
.showToast({
|
|
73
|
+
body: {
|
|
74
|
+
title: "Memory Explorer",
|
|
75
|
+
message: `Web UI available at ${url}`,
|
|
76
|
+
variant: "info",
|
|
77
|
+
duration: 3000,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.catch(() => { });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.catch((error) => {
|
|
85
|
+
log("Web server failed to start", { error: String(error) });
|
|
86
|
+
if (ctx.client?.tui) {
|
|
87
|
+
ctx.client.tui
|
|
88
|
+
.showToast({
|
|
89
|
+
body: {
|
|
90
|
+
title: "Memory Explorer Error",
|
|
91
|
+
message: `Failed to start: ${String(error)}`,
|
|
92
|
+
variant: "error",
|
|
93
|
+
duration: 5000,
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
.catch(() => { });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const shutdownHandler = async () => {
|
|
101
|
+
if (webServer) {
|
|
102
|
+
await webServer.stop();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
process.on("SIGINT", shutdownHandler);
|
|
106
|
+
process.on("SIGTERM", shutdownHandler);
|
|
107
|
+
process.on("exit", shutdownHandler);
|
|
108
|
+
return {
|
|
109
|
+
"chat.message": async (input, output) => {
|
|
110
|
+
if (!isConfigured())
|
|
111
|
+
return;
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
try {
|
|
114
|
+
const textParts = output.parts.filter((p) => p.type === "text");
|
|
115
|
+
if (textParts.length === 0)
|
|
116
|
+
return;
|
|
117
|
+
const userMessage = textParts.map((p) => p.text).join("\n");
|
|
118
|
+
if (!userMessage.trim())
|
|
119
|
+
return;
|
|
120
|
+
if (detectMemoryKeyword(userMessage)) {
|
|
121
|
+
const nudgePart = {
|
|
122
|
+
id: `memory-nudge-${Date.now()}`,
|
|
123
|
+
sessionID: input.sessionID,
|
|
124
|
+
messageID: output.message.id,
|
|
125
|
+
type: "text",
|
|
126
|
+
text: MEMORY_NUDGE_MESSAGE,
|
|
127
|
+
synthetic: true,
|
|
128
|
+
};
|
|
129
|
+
output.parts.push(nudgePart);
|
|
130
|
+
}
|
|
131
|
+
const isFirstMessage = !injectedSessions.has(input.sessionID);
|
|
132
|
+
if (isFirstMessage) {
|
|
133
|
+
injectedSessions.add(input.sessionID);
|
|
134
|
+
const needsWarmup = !(await memoryClient.isReady());
|
|
135
|
+
if (needsWarmup) {
|
|
136
|
+
if (ctx.client?.tui) {
|
|
137
|
+
await ctx.client.tui
|
|
138
|
+
.showToast({
|
|
139
|
+
body: {
|
|
140
|
+
title: "Memory System",
|
|
141
|
+
message: "Initializing (first time: 30-60s)...",
|
|
142
|
+
variant: "info",
|
|
143
|
+
duration: 5000,
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
.catch(() => { });
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
await memoryClient.warmup();
|
|
150
|
+
if (ctx.client?.tui) {
|
|
151
|
+
const autoCaptureStatus = autoCaptureService.isEnabled()
|
|
152
|
+
? "Auto-capture: enabled"
|
|
153
|
+
: autoCaptureService.getDisabledReason() || "Auto-capture: disabled";
|
|
154
|
+
await ctx.client.tui
|
|
155
|
+
.showToast({
|
|
156
|
+
body: {
|
|
157
|
+
title: "Memory System Ready!",
|
|
158
|
+
message: autoCaptureStatus,
|
|
159
|
+
variant: autoCaptureService.isEnabled() ? "success" : "warning",
|
|
160
|
+
duration: 3000,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
.catch(() => { });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (warmupError) {
|
|
167
|
+
log("Warmup failed", { error: String(warmupError) });
|
|
168
|
+
if (ctx.client?.tui) {
|
|
169
|
+
await ctx.client.tui
|
|
170
|
+
.showToast({
|
|
171
|
+
body: {
|
|
172
|
+
title: "Memory System Error",
|
|
173
|
+
message: `Failed to initialize: ${String(warmupError)}`,
|
|
174
|
+
variant: "error",
|
|
175
|
+
duration: 10000,
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
.catch(() => { });
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const [profileResult, userMemoriesResult, projectMemoriesListResult] = await Promise.all([
|
|
184
|
+
memoryClient.getProfile(tags.user.tag, userMessage),
|
|
185
|
+
memoryClient.searchMemories(userMessage, tags.user.tag),
|
|
186
|
+
memoryClient.listMemories(tags.project.tag, CONFIG.maxProjectMemories),
|
|
187
|
+
]);
|
|
188
|
+
const profile = profileResult.success ? profileResult : null;
|
|
189
|
+
const userMemories = userMemoriesResult.success ? userMemoriesResult : { results: [] };
|
|
190
|
+
const projectMemoriesList = projectMemoriesListResult.success
|
|
191
|
+
? projectMemoriesListResult
|
|
192
|
+
: { memories: [] };
|
|
193
|
+
const projectMemories = {
|
|
194
|
+
results: (projectMemoriesList.memories || []).map((m) => ({
|
|
195
|
+
id: m.id,
|
|
196
|
+
memory: m.summary,
|
|
197
|
+
similarity: 1,
|
|
198
|
+
title: m.title,
|
|
199
|
+
metadata: m.metadata,
|
|
200
|
+
})),
|
|
201
|
+
total: projectMemoriesList.memories?.length || 0,
|
|
202
|
+
timing: 0,
|
|
203
|
+
};
|
|
204
|
+
const memoryContext = formatContextForPrompt(profile, userMemories, projectMemories);
|
|
205
|
+
if (memoryContext) {
|
|
206
|
+
const contextPart = {
|
|
207
|
+
id: `memory-context-${Date.now()}`,
|
|
208
|
+
sessionID: input.sessionID,
|
|
209
|
+
messageID: output.message.id,
|
|
210
|
+
type: "text",
|
|
211
|
+
text: memoryContext,
|
|
212
|
+
synthetic: true,
|
|
213
|
+
};
|
|
214
|
+
output.parts.unshift(contextPart);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
log("chat.message: ERROR", { error: String(error) });
|
|
220
|
+
if (ctx.client?.tui) {
|
|
221
|
+
await ctx.client.tui
|
|
222
|
+
.showToast({
|
|
223
|
+
body: {
|
|
224
|
+
title: "Memory System Error",
|
|
225
|
+
message: String(error),
|
|
226
|
+
variant: "error",
|
|
227
|
+
duration: 5000,
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
.catch(() => { });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
tool: {
|
|
235
|
+
memory: tool({
|
|
236
|
+
description: "Manage and query the local persistent memory system. Use 'search' to find relevant memories, 'add' to store new knowledge, 'profile' to view user profile, 'list' to see recent memories, 'forget' to remove a memory.",
|
|
237
|
+
args: {
|
|
238
|
+
mode: tool.schema
|
|
239
|
+
.enum([
|
|
240
|
+
"add",
|
|
241
|
+
"search",
|
|
242
|
+
"profile",
|
|
243
|
+
"list",
|
|
244
|
+
"forget",
|
|
245
|
+
"help",
|
|
246
|
+
"capture-now",
|
|
247
|
+
"auto-capture-toggle",
|
|
248
|
+
"auto-capture-stats",
|
|
249
|
+
])
|
|
250
|
+
.optional(),
|
|
251
|
+
content: tool.schema.string().optional(),
|
|
252
|
+
query: tool.schema.string().optional(),
|
|
253
|
+
type: tool.schema.string().optional(),
|
|
254
|
+
scope: tool.schema.enum(["user", "project"]).optional(),
|
|
255
|
+
memoryId: tool.schema.string().optional(),
|
|
256
|
+
limit: tool.schema.number().optional(),
|
|
257
|
+
},
|
|
258
|
+
async execute(args, toolCtx) {
|
|
259
|
+
if (!isConfigured()) {
|
|
260
|
+
return JSON.stringify({
|
|
261
|
+
success: false,
|
|
262
|
+
error: "Memory system not configured properly.",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const needsWarmup = !(await memoryClient.isReady());
|
|
266
|
+
if (needsWarmup) {
|
|
267
|
+
return JSON.stringify({
|
|
268
|
+
success: false,
|
|
269
|
+
error: "Memory system is initializing. Please wait a moment and try again.",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const mode = args.mode || "help";
|
|
273
|
+
try {
|
|
274
|
+
switch (mode) {
|
|
275
|
+
case "help": {
|
|
276
|
+
return JSON.stringify({
|
|
277
|
+
success: true,
|
|
278
|
+
message: "Memory System Usage Guide",
|
|
279
|
+
commands: [
|
|
280
|
+
{
|
|
281
|
+
command: "add",
|
|
282
|
+
description: "Store a new memory",
|
|
283
|
+
args: ["content", "type?", "scope?"],
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
command: "search",
|
|
287
|
+
description: "Search memories",
|
|
288
|
+
args: ["query", "scope?"],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
command: "profile",
|
|
292
|
+
description: "View user profile",
|
|
293
|
+
args: ["query?"],
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
command: "list",
|
|
297
|
+
description: "List recent memories",
|
|
298
|
+
args: ["scope?", "limit?"],
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
command: "forget",
|
|
302
|
+
description: "Remove a memory",
|
|
303
|
+
args: ["memoryId", "scope?"],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
command: "capture-now",
|
|
307
|
+
description: "Manually trigger memory capture for current session",
|
|
308
|
+
args: [],
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
command: "auto-capture-toggle",
|
|
312
|
+
description: "Enable/disable automatic memory capture",
|
|
313
|
+
args: [],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
command: "auto-capture-stats",
|
|
317
|
+
description: "View auto-capture statistics for current session",
|
|
318
|
+
args: [],
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
scopes: {
|
|
322
|
+
user: "Cross-project user behaviors, preferences, patterns, requests",
|
|
323
|
+
project: "Project-specific knowledge, decisions, architecture, context",
|
|
324
|
+
},
|
|
325
|
+
typeGuidance: "Choose appropriate type: preference, architecture, workflow, bug-fix, configuration, pattern, request, context, etc. Be specific and descriptive with categories.",
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
case "add": {
|
|
329
|
+
if (!args.content) {
|
|
330
|
+
return JSON.stringify({
|
|
331
|
+
success: false,
|
|
332
|
+
error: "content parameter is required for add mode",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const sanitizedContent = stripPrivateContent(args.content);
|
|
336
|
+
if (isFullyPrivate(args.content)) {
|
|
337
|
+
return JSON.stringify({
|
|
338
|
+
success: false,
|
|
339
|
+
error: "Cannot store fully private content",
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const scope = args.scope || "project";
|
|
343
|
+
const tagInfo = scope === "user" ? tags.user : tags.project;
|
|
344
|
+
const result = await memoryClient.addMemory(sanitizedContent, tagInfo.tag, {
|
|
345
|
+
type: args.type,
|
|
346
|
+
displayName: tagInfo.displayName,
|
|
347
|
+
userName: tagInfo.userName,
|
|
348
|
+
userEmail: tagInfo.userEmail,
|
|
349
|
+
projectPath: tagInfo.projectPath,
|
|
350
|
+
projectName: tagInfo.projectName,
|
|
351
|
+
gitRepoUrl: tagInfo.gitRepoUrl,
|
|
352
|
+
});
|
|
353
|
+
if (!result.success) {
|
|
354
|
+
return JSON.stringify({
|
|
355
|
+
success: false,
|
|
356
|
+
error: result.error || "Failed to add memory",
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return JSON.stringify({
|
|
360
|
+
success: true,
|
|
361
|
+
message: `Memory added to ${scope} scope`,
|
|
362
|
+
id: result.id,
|
|
363
|
+
scope,
|
|
364
|
+
type: args.type,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
case "search": {
|
|
368
|
+
if (!args.query) {
|
|
369
|
+
return JSON.stringify({
|
|
370
|
+
success: false,
|
|
371
|
+
error: "query parameter is required for search mode",
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const scope = args.scope;
|
|
375
|
+
if (scope === "user") {
|
|
376
|
+
const result = await memoryClient.searchMemories(args.query, tags.user.tag);
|
|
377
|
+
if (!result.success) {
|
|
378
|
+
return JSON.stringify({
|
|
379
|
+
success: false,
|
|
380
|
+
error: result.error || "Failed to search memories",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return formatSearchResults(args.query, scope, result, args.limit);
|
|
384
|
+
}
|
|
385
|
+
if (scope === "project") {
|
|
386
|
+
const result = await memoryClient.searchMemories(args.query, tags.project.tag);
|
|
387
|
+
if (!result.success) {
|
|
388
|
+
return JSON.stringify({
|
|
389
|
+
success: false,
|
|
390
|
+
error: result.error || "Failed to search memories",
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return formatSearchResults(args.query, scope, result, args.limit);
|
|
394
|
+
}
|
|
395
|
+
const [userResult, projectResult] = await Promise.all([
|
|
396
|
+
memoryClient.searchMemories(args.query, tags.user.tag),
|
|
397
|
+
memoryClient.searchMemories(args.query, tags.project.tag),
|
|
398
|
+
]);
|
|
399
|
+
if (!userResult.success || !projectResult.success) {
|
|
400
|
+
return JSON.stringify({
|
|
401
|
+
success: false,
|
|
402
|
+
error: userResult.error || projectResult.error || "Failed to search memories",
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const combined = [
|
|
406
|
+
...(userResult.results || []).map((r) => ({
|
|
407
|
+
...r,
|
|
408
|
+
scope: "user",
|
|
409
|
+
})),
|
|
410
|
+
...(projectResult.results || []).map((r) => ({
|
|
411
|
+
...r,
|
|
412
|
+
scope: "project",
|
|
413
|
+
})),
|
|
414
|
+
].sort((a, b) => b.similarity - a.similarity);
|
|
415
|
+
return JSON.stringify({
|
|
416
|
+
success: true,
|
|
417
|
+
query: args.query,
|
|
418
|
+
count: combined.length,
|
|
419
|
+
results: combined.slice(0, args.limit || 10).map((r) => ({
|
|
420
|
+
id: r.id,
|
|
421
|
+
content: r.memory || r.chunk,
|
|
422
|
+
similarity: Math.round(r.similarity * 100),
|
|
423
|
+
scope: r.scope,
|
|
424
|
+
})),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
case "profile": {
|
|
428
|
+
const result = await memoryClient.getProfile(tags.user.tag, args.query);
|
|
429
|
+
if (!result.success) {
|
|
430
|
+
return JSON.stringify({
|
|
431
|
+
success: false,
|
|
432
|
+
error: result.error || "Failed to fetch profile",
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return JSON.stringify({
|
|
436
|
+
success: true,
|
|
437
|
+
profile: {
|
|
438
|
+
static: result.profile?.static || [],
|
|
439
|
+
dynamic: result.profile?.dynamic || [],
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
case "list": {
|
|
444
|
+
const scope = args.scope || "project";
|
|
445
|
+
const limit = args.limit || 20;
|
|
446
|
+
const tagInfo = scope === "user" ? tags.user : tags.project;
|
|
447
|
+
const result = await memoryClient.listMemories(tagInfo.tag, limit);
|
|
448
|
+
if (!result.success) {
|
|
449
|
+
return JSON.stringify({
|
|
450
|
+
success: false,
|
|
451
|
+
error: result.error || "Failed to list memories",
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
const memories = result.memories || [];
|
|
455
|
+
return JSON.stringify({
|
|
456
|
+
success: true,
|
|
457
|
+
scope,
|
|
458
|
+
count: memories.length,
|
|
459
|
+
memories: memories.map((m) => ({
|
|
460
|
+
id: m.id,
|
|
461
|
+
content: m.summary,
|
|
462
|
+
createdAt: m.createdAt,
|
|
463
|
+
metadata: m.metadata,
|
|
464
|
+
})),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
case "forget": {
|
|
468
|
+
if (!args.memoryId) {
|
|
469
|
+
return JSON.stringify({
|
|
470
|
+
success: false,
|
|
471
|
+
error: "memoryId parameter is required for forget mode",
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const scope = args.scope || "project";
|
|
475
|
+
const result = await memoryClient.deleteMemory(args.memoryId);
|
|
476
|
+
if (!result.success) {
|
|
477
|
+
return JSON.stringify({
|
|
478
|
+
success: false,
|
|
479
|
+
error: result.error || "Failed to delete memory",
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return JSON.stringify({
|
|
483
|
+
success: true,
|
|
484
|
+
message: `Memory ${args.memoryId} removed from ${scope} scope`,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
case "capture-now": {
|
|
488
|
+
await performAutoCapture(ctx, autoCaptureService, toolCtx.sessionID, directory);
|
|
489
|
+
return JSON.stringify({
|
|
490
|
+
success: true,
|
|
491
|
+
message: "Manual capture triggered",
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
case "auto-capture-toggle": {
|
|
495
|
+
const enabled = autoCaptureService.toggle();
|
|
496
|
+
return JSON.stringify({
|
|
497
|
+
success: true,
|
|
498
|
+
message: `Auto-capture ${enabled ? "enabled" : "disabled"}`,
|
|
499
|
+
enabled,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
case "auto-capture-stats": {
|
|
503
|
+
const stats = autoCaptureService.getStats(toolCtx.sessionID);
|
|
504
|
+
if (!stats) {
|
|
505
|
+
return JSON.stringify({
|
|
506
|
+
success: true,
|
|
507
|
+
message: "No capture data for this session",
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return JSON.stringify({
|
|
511
|
+
success: true,
|
|
512
|
+
stats: {
|
|
513
|
+
lastCaptureTokens: stats.lastCaptureTokens,
|
|
514
|
+
minutesSinceCapture: Math.floor(stats.timeSinceCapture / 60000),
|
|
515
|
+
tokenThreshold: CONFIG.autoCaptureTokenThreshold,
|
|
516
|
+
minTokens: CONFIG.autoCaptureMinTokens,
|
|
517
|
+
enabled: autoCaptureService.isEnabled(),
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
default:
|
|
522
|
+
return JSON.stringify({
|
|
523
|
+
success: false,
|
|
524
|
+
error: `Unknown mode: ${mode}`,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
return JSON.stringify({
|
|
530
|
+
success: false,
|
|
531
|
+
error: error instanceof Error ? error.message : String(error),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
}),
|
|
536
|
+
},
|
|
537
|
+
event: async (input) => {
|
|
538
|
+
const event = input.event;
|
|
539
|
+
const props = event.properties;
|
|
540
|
+
if (event.type === "message.updated") {
|
|
541
|
+
if (!autoCaptureService.isEnabled())
|
|
542
|
+
return;
|
|
543
|
+
const info = props?.info;
|
|
544
|
+
if (!info)
|
|
545
|
+
return;
|
|
546
|
+
const sessionID = info.sessionID;
|
|
547
|
+
if (!sessionID)
|
|
548
|
+
return;
|
|
549
|
+
if (info.role !== "assistant" || !info.finish)
|
|
550
|
+
return;
|
|
551
|
+
const tokens = info.tokens;
|
|
552
|
+
if (!tokens)
|
|
553
|
+
return;
|
|
554
|
+
const totalUsed = tokens.input + tokens.cache.read + tokens.output;
|
|
555
|
+
const shouldCapture = autoCaptureService.checkTokenThreshold(sessionID, totalUsed);
|
|
556
|
+
if (shouldCapture) {
|
|
557
|
+
performAutoCapture(ctx, autoCaptureService, sessionID, directory).catch((err) => log("Auto-capture failed", { error: String(err) }));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (event.type === "session.deleted" && props?.sessionID) {
|
|
561
|
+
autoCaptureService.cleanup(props.sessionID);
|
|
562
|
+
}
|
|
563
|
+
if (event.type === "session.idle") {
|
|
564
|
+
if (!isConfigured())
|
|
565
|
+
return;
|
|
566
|
+
const { cleanupService } = await import("./services/cleanup-service.js");
|
|
567
|
+
const shouldRun = await cleanupService.shouldRunCleanup();
|
|
568
|
+
if (!shouldRun)
|
|
569
|
+
return;
|
|
570
|
+
cleanupService
|
|
571
|
+
.runCleanup()
|
|
572
|
+
.then((result) => {
|
|
573
|
+
if (result.deletedCount > 0 && ctx.client?.tui) {
|
|
574
|
+
ctx.client.tui
|
|
575
|
+
.showToast({
|
|
576
|
+
body: {
|
|
577
|
+
title: "Memory Cleanup",
|
|
578
|
+
message: `Deleted ${result.deletedCount} old memories (user: ${result.userCount}, project: ${result.projectCount})`,
|
|
579
|
+
variant: "info",
|
|
580
|
+
duration: 5000,
|
|
581
|
+
},
|
|
582
|
+
})
|
|
583
|
+
.catch(() => { });
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
.catch((err) => {
|
|
587
|
+
log("Auto-cleanup failed", { error: String(err) });
|
|
588
|
+
if (ctx.client?.tui) {
|
|
589
|
+
ctx.client.tui
|
|
590
|
+
.showToast({
|
|
591
|
+
body: {
|
|
592
|
+
title: "Memory Cleanup Error",
|
|
593
|
+
message: String(err),
|
|
594
|
+
variant: "error",
|
|
595
|
+
duration: 5000,
|
|
596
|
+
},
|
|
597
|
+
})
|
|
598
|
+
.catch(() => { });
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
function formatSearchResults(query, scope, results, limit) {
|
|
606
|
+
const memoryResults = results.results || [];
|
|
607
|
+
return JSON.stringify({
|
|
608
|
+
success: true,
|
|
609
|
+
query,
|
|
610
|
+
scope,
|
|
611
|
+
count: memoryResults.length,
|
|
612
|
+
results: memoryResults.slice(0, limit || 10).map((r) => ({
|
|
613
|
+
id: r.id,
|
|
614
|
+
content: r.memory || r.chunk,
|
|
615
|
+
similarity: Math.round(r.similarity * 100),
|
|
616
|
+
})),
|
|
617
|
+
});
|
|
618
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";AAgBA,QAAA,MAAQ,iBAAiB,sCAA+B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,eAAe,iBAAiB,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const projectRoot = join(__dirname, "..");
|
|
8
|
+
if (!existsSync(join(projectRoot, "node_modules"))) {
|
|
9
|
+
console.error("Error: node_modules not found. Run 'bun install' first.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
process.chdir(projectRoot);
|
|
13
|
+
const { OpenCodeMemPlugin } = await import("./index.js");
|
|
14
|
+
export { OpenCodeMemPlugin };
|
|
15
|
+
export default OpenCodeMemPlugin;
|