opencode-memelord 0.0.1 → 0.1.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/dist/embedder.d.ts +11 -0
- package/dist/hooks.d.ts +22 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +626 -116
- package/dist/store.d.ts +44 -0
- package/dist/tools.d.ts +84 -0
- package/package.json +8 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy-loaded local embedding function using @huggingface/transformers.
|
|
3
|
+
* Downloads the model on first use and caches it locally.
|
|
4
|
+
*
|
|
5
|
+
* Default: Xenova/all-MiniLM-L6-v2 (384 dimensions, ~22M params)
|
|
6
|
+
* With quantized=true (default), uses the q8 (8-bit) ONNX variant.
|
|
7
|
+
*
|
|
8
|
+
* Verbatim from memelord's packages/cli/src/embedder.ts.
|
|
9
|
+
*/
|
|
10
|
+
import type { EmbedFn } from 'memelord';
|
|
11
|
+
export declare function createEmbedder(): Promise<EmbedFn>;
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import type { StoreManager } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* session.created → inject top memories into context.
|
|
5
|
+
* Equivalent to memelord's hookSessionStart.
|
|
6
|
+
*/
|
|
7
|
+
export declare function onSessionCreated(sessionId: string, client: PluginInput['client'], storeManager: StoreManager): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* tool.execute.after → record tool failures.
|
|
10
|
+
* Equivalent to memelord's hookPostToolUse.
|
|
11
|
+
*/
|
|
12
|
+
export declare function onToolAfter(sessionId: string, toolName: string, toolInput: unknown, toolOutput: string, toolMetadata: unknown, storeManager: StoreManager): void;
|
|
13
|
+
/**
|
|
14
|
+
* session.idle → analyze transcript for corrections, discoveries, and failure patterns.
|
|
15
|
+
* Equivalent to memelord's hookStop.
|
|
16
|
+
*/
|
|
17
|
+
export declare function onSessionIdle(sessionId: string, client: PluginInput['client'], storeManager: StoreManager): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* session.deleted → embed pending memories, run decay, clean up.
|
|
20
|
+
* Equivalent to memelord's hookSessionEnd.
|
|
21
|
+
*/
|
|
22
|
+
export declare function onSessionDeleted(sessionId: string, storeManager: StoreManager): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* opencode-memelord: OpenCode plugin for memelord persistent memory.
|
|
2
|
+
* opencode-memelord: Standalone OpenCode plugin for memelord persistent memory.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Uses the memelord SDK directly — no MCP server, no `memelord init`,
|
|
5
|
+
* no per-project configuration needed. Databases are stored globally
|
|
6
|
+
* at ~/.config/memelord/projects/<project-key>/memory.db, keyed by
|
|
7
|
+
* git remote URL so multiple worktrees share the same database.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* session.
|
|
12
|
-
*
|
|
9
|
+
* Provides:
|
|
10
|
+
* - 5 custom tools (memory_start_task, memory_report, memory_end_task,
|
|
11
|
+
* memory_contradict, memory_status) replacing the MCP server
|
|
12
|
+
* - Lifecycle hooks (session.created, tool.execute.after, session.idle,
|
|
13
|
+
* session.deleted) for automatic memory instrumentation
|
|
13
14
|
*/
|
|
14
|
-
import type { Plugin } from
|
|
15
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
15
16
|
export declare const MemelordPlugin: Plugin;
|
package/dist/index.js
CHANGED
|
@@ -1,150 +1,660 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/store.ts
|
|
21
|
+
import { createMemoryStore } from "memelord";
|
|
22
|
+
import { createHash } from "crypto";
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { join } from "path";
|
|
25
|
+
import {
|
|
26
|
+
existsSync,
|
|
27
|
+
mkdirSync,
|
|
28
|
+
readFileSync,
|
|
29
|
+
writeFileSync,
|
|
30
|
+
appendFileSync,
|
|
31
|
+
unlinkSync
|
|
32
|
+
} from "fs";
|
|
33
|
+
|
|
34
|
+
// src/embedder.ts
|
|
35
|
+
var cachedEmbedder = null;
|
|
36
|
+
async function createEmbedder() {
|
|
37
|
+
if (cachedEmbedder)
|
|
38
|
+
return cachedEmbedder;
|
|
39
|
+
const { pipeline } = await import("@huggingface/transformers");
|
|
40
|
+
const model = process.env.MEMELORD_MODEL ?? "Xenova/all-MiniLM-L6-v2";
|
|
41
|
+
const extractor = await pipeline("feature-extraction", model, {
|
|
42
|
+
quantized: true
|
|
43
|
+
});
|
|
44
|
+
cachedEmbedder = async (text) => {
|
|
45
|
+
const output = await extractor(text, { pooling: "mean", normalize: true });
|
|
46
|
+
return new Float32Array(output.data);
|
|
47
|
+
};
|
|
48
|
+
return cachedEmbedder;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/store.ts
|
|
52
|
+
var projectKeyCache = new Map;
|
|
53
|
+
async function getProjectKey(worktree, $) {
|
|
54
|
+
const cached = projectKeyCache.get(worktree);
|
|
55
|
+
if (cached)
|
|
56
|
+
return cached;
|
|
57
|
+
let source = worktree;
|
|
7
58
|
try {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
59
|
+
const remoteUrl = await $`git -C ${worktree} remote get-url origin`.quiet().nothrow().text();
|
|
60
|
+
if (remoteUrl.trim()) {
|
|
61
|
+
source = remoteUrl.trim();
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
const key = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
65
|
+
projectKeyCache.set(worktree, key);
|
|
66
|
+
return key;
|
|
67
|
+
}
|
|
68
|
+
function getProjectDir(projectKey) {
|
|
69
|
+
const dir = join(homedir(), ".config", "memelord", "projects", projectKey);
|
|
70
|
+
if (!existsSync(dir))
|
|
71
|
+
mkdirSync(dir, { recursive: true });
|
|
72
|
+
return dir;
|
|
14
73
|
}
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
74
|
+
function getSessionsDir(projectKey) {
|
|
75
|
+
const dir = join(getProjectDir(projectKey), "sessions");
|
|
76
|
+
if (!existsSync(dir))
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
return dir;
|
|
79
|
+
}
|
|
80
|
+
function sessionFilePath(projectKey, sessionId) {
|
|
81
|
+
return join(getSessionsDir(projectKey), `${sessionId}.json`);
|
|
82
|
+
}
|
|
83
|
+
function failuresFilePath(projectKey, sessionId) {
|
|
84
|
+
return join(getSessionsDir(projectKey), `${sessionId}.failures.jsonl`);
|
|
85
|
+
}
|
|
86
|
+
async function createStoreManager(worktree, $) {
|
|
87
|
+
const projectKey = await getProjectKey(worktree, $);
|
|
88
|
+
const dbPath = join(getProjectDir(projectKey), "memory.db");
|
|
89
|
+
const dummyEmbed = async () => new Float32Array(384);
|
|
90
|
+
let currentSessionId = "unknown";
|
|
91
|
+
let lightStore = null;
|
|
92
|
+
let liveStore = null;
|
|
93
|
+
let lightStoreSessionId = "";
|
|
94
|
+
let liveStoreSessionId = "";
|
|
95
|
+
function setCurrentSessionId(sessionId) {
|
|
96
|
+
currentSessionId = sessionId;
|
|
97
|
+
}
|
|
98
|
+
async function getStore() {
|
|
99
|
+
if (!lightStore || lightStoreSessionId !== currentSessionId) {
|
|
100
|
+
if (lightStore)
|
|
101
|
+
await lightStore.close().catch(() => {});
|
|
102
|
+
lightStore = createMemoryStore({
|
|
103
|
+
dbPath,
|
|
104
|
+
sessionId: currentSessionId,
|
|
105
|
+
embed: dummyEmbed
|
|
106
|
+
});
|
|
107
|
+
await lightStore.init();
|
|
108
|
+
lightStoreSessionId = currentSessionId;
|
|
109
|
+
}
|
|
110
|
+
return lightStore;
|
|
111
|
+
}
|
|
112
|
+
async function getLiveStore() {
|
|
113
|
+
if (!liveStore || liveStoreSessionId !== currentSessionId) {
|
|
114
|
+
if (liveStore)
|
|
115
|
+
await liveStore.close().catch(() => {});
|
|
116
|
+
const embed = await createEmbedder();
|
|
117
|
+
liveStore = createMemoryStore({
|
|
118
|
+
dbPath,
|
|
119
|
+
sessionId: currentSessionId,
|
|
120
|
+
embed
|
|
121
|
+
});
|
|
122
|
+
await liveStore.init();
|
|
123
|
+
liveStoreSessionId = currentSessionId;
|
|
124
|
+
}
|
|
125
|
+
return liveStore;
|
|
126
|
+
}
|
|
127
|
+
function getSessionState(sessionId) {
|
|
128
|
+
const path = sessionFilePath(projectKey, sessionId);
|
|
129
|
+
if (!existsSync(path))
|
|
130
|
+
return null;
|
|
21
131
|
try {
|
|
22
|
-
|
|
23
|
-
return await $`echo ${json} | node ${memelordBin} hook ${event}`.quiet().nothrow().text();
|
|
24
|
-
}
|
|
25
|
-
return await $`echo ${json} | memelord hook ${event}`.quiet().nothrow().text();
|
|
132
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
26
133
|
} catch {
|
|
27
|
-
return
|
|
134
|
+
return null;
|
|
28
135
|
}
|
|
29
136
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
137
|
+
function setSessionState(sessionId, state) {
|
|
138
|
+
writeFileSync(sessionFilePath(projectKey, sessionId), JSON.stringify(state));
|
|
139
|
+
}
|
|
140
|
+
function getFailures(sessionId) {
|
|
141
|
+
const path = failuresFilePath(projectKey, sessionId);
|
|
142
|
+
if (!existsSync(path))
|
|
143
|
+
return [];
|
|
144
|
+
try {
|
|
145
|
+
return readFileSync(path, "utf-8").trim().split(`
|
|
146
|
+
`).map((line) => {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(line);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}).filter(Boolean);
|
|
153
|
+
} catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function appendFailure(sessionId, failure) {
|
|
158
|
+
appendFileSync(failuresFilePath(projectKey, sessionId), JSON.stringify(failure) + `
|
|
159
|
+
`);
|
|
160
|
+
}
|
|
161
|
+
function cleanupSession(sessionId) {
|
|
162
|
+
const sf = sessionFilePath(projectKey, sessionId);
|
|
163
|
+
const ff = failuresFilePath(projectKey, sessionId);
|
|
164
|
+
if (existsSync(sf))
|
|
165
|
+
try {
|
|
166
|
+
unlinkSync(sf);
|
|
167
|
+
} catch {}
|
|
168
|
+
if (existsSync(ff))
|
|
169
|
+
try {
|
|
170
|
+
unlinkSync(ff);
|
|
171
|
+
} catch {}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
getStore,
|
|
175
|
+
getLiveStore,
|
|
176
|
+
setCurrentSessionId,
|
|
177
|
+
getSessionState,
|
|
178
|
+
setSessionState,
|
|
179
|
+
getFailures,
|
|
180
|
+
appendFailure,
|
|
181
|
+
cleanupSession
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/tools.ts
|
|
186
|
+
import { tool } from "@opencode-ai/plugin";
|
|
187
|
+
function createTools(storeManager) {
|
|
188
|
+
return {
|
|
189
|
+
memory_start_task: tool({
|
|
190
|
+
description: `Start a new task and retrieve relevant memories. You MUST call this FIRST at the beginning of every task, before doing any work. Pass the user's request or task description.
|
|
191
|
+
|
|
192
|
+
Returns memories from past experience that are relevant to this task. These memories contain lessons learned from previous sessions — patterns that worked, mistakes to avoid, project-specific knowledge. Using them will help you avoid repeating past mistakes and work more efficiently.
|
|
193
|
+
|
|
194
|
+
Also returns a task_id that you MUST pass to memory_end_task when you finish.`,
|
|
195
|
+
args: {
|
|
196
|
+
description: tool.schema.string().describe("The user's request or task description")
|
|
197
|
+
},
|
|
198
|
+
async execute(args) {
|
|
199
|
+
try {
|
|
200
|
+
const store = await storeManager.getLiveStore();
|
|
201
|
+
try {
|
|
202
|
+
await store.embedPending();
|
|
203
|
+
} catch {}
|
|
204
|
+
const result = await store.startTask(args.description);
|
|
205
|
+
let text = `Task started (id: ${result.taskId})
|
|
206
|
+
|
|
207
|
+
`;
|
|
208
|
+
if (result.memories.length === 0) {
|
|
209
|
+
text += "No relevant memories found. This appears to be a new type of task.";
|
|
210
|
+
} else {
|
|
211
|
+
text += `Retrieved ${result.memories.length} relevant memories:
|
|
212
|
+
|
|
213
|
+
`;
|
|
214
|
+
for (const mem of result.memories) {
|
|
215
|
+
text += `--- [${mem.category}] (weight: ${mem.weight.toFixed(2)}, score: ${mem.score.toFixed(3)}) ---
|
|
216
|
+
`;
|
|
217
|
+
text += `${mem.content}
|
|
218
|
+
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return text;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
return `Error: ${e.message}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}),
|
|
228
|
+
memory_report: tool({
|
|
229
|
+
description: `Report a self-correction or user-provided knowledge to persist across sessions.
|
|
230
|
+
|
|
231
|
+
Use type "correction" when you:
|
|
232
|
+
- Tried the wrong file path, function name, or API and had to search for the correct one
|
|
233
|
+
- Used the wrong tool, command, or pattern for this project
|
|
234
|
+
- Made an architectural assumption that turned out wrong
|
|
235
|
+
- Wasted significant effort (3+ tool calls) on a wrong approach before finding the right one
|
|
236
|
+
Do NOT report: typos, minor first-try search misses, or things that took <2 tool calls to resolve.
|
|
237
|
+
|
|
238
|
+
Use type "user_input" when:
|
|
239
|
+
- The user denies a tool call and explains why
|
|
240
|
+
- The user corrects your approach ("no, use X instead of Y")
|
|
241
|
+
- The user shares project-specific knowledge ("we use Turso, not SQLite")
|
|
242
|
+
- The user states a preference ("always run tests before committing")
|
|
243
|
+
The user should never have to tell you the same thing twice.
|
|
244
|
+
|
|
245
|
+
Use type "insight" when you discover something useful about the codebase during exploration:
|
|
246
|
+
- Key file locations ("auth middleware is in src/middleware/auth.rs")
|
|
247
|
+
- Architecture patterns ("the project uses a VDBE architecture: translate -> bytecode -> execute")
|
|
248
|
+
- Build/test conventions ("run 'make check' before committing, tests are in tests/")
|
|
249
|
+
- Important relationships between components
|
|
250
|
+
Store insights proactively — they save future sessions from re-exploring the same codebase.`,
|
|
251
|
+
args: {
|
|
252
|
+
type: tool.schema.enum(["correction", "user_input", "insight"]).describe("What kind of memory: correction (self-correction), user_input (user-provided knowledge), or insight (codebase knowledge discovered during exploration)"),
|
|
253
|
+
lesson: tool.schema.string().describe("The lesson learned — what the agent should remember"),
|
|
254
|
+
what_failed: tool.schema.string().optional().describe("(correction only) The approach that failed"),
|
|
255
|
+
what_worked: tool.schema.string().optional().describe("(correction only) The approach that worked"),
|
|
256
|
+
tokens_wasted: tool.schema.number().optional().describe("(correction only) Approximate tokens spent on the wrong approach"),
|
|
257
|
+
tools_wasted: tool.schema.number().optional().describe("(correction only) Number of tool calls wasted"),
|
|
258
|
+
source: tool.schema.enum(["user_denial", "user_correction", "user_input"]).optional().describe("(user_input only) How the user provided this")
|
|
259
|
+
},
|
|
260
|
+
async execute(args) {
|
|
261
|
+
try {
|
|
262
|
+
const store = await storeManager.getStore();
|
|
263
|
+
let id;
|
|
264
|
+
if (args.type === "correction") {
|
|
265
|
+
id = await store.reportCorrection({
|
|
266
|
+
lesson: args.lesson,
|
|
267
|
+
whatFailed: args.what_failed ?? "",
|
|
268
|
+
whatWorked: args.what_worked ?? "",
|
|
269
|
+
tokensWasted: args.tokens_wasted,
|
|
270
|
+
toolsWasted: args.tools_wasted
|
|
50
271
|
});
|
|
272
|
+
} else if (args.type === "insight") {
|
|
273
|
+
id = await store.insertRawMemory(args.lesson, "insight", 1);
|
|
274
|
+
} else {
|
|
275
|
+
id = await store.reportUserInput({
|
|
276
|
+
lesson: args.lesson,
|
|
277
|
+
source: args.source ?? "user_input"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return `Memory saved (id: ${id}). This will be remembered across sessions.`;
|
|
281
|
+
} catch (e) {
|
|
282
|
+
return `Error: ${e.message}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
memory_end_task: tool({
|
|
287
|
+
description: `End the current task and provide outcome metrics. You MUST call this when you finish a task or when the user moves on to a different request. This updates memory weights so that useful memories are strengthened and unhelpful ones decay.
|
|
288
|
+
|
|
289
|
+
For self_report: rate each memory that was retrieved at task start:
|
|
290
|
+
0 = ignored / not relevant
|
|
291
|
+
1 = glanced at but didn't use
|
|
292
|
+
2 = somewhat useful
|
|
293
|
+
3 = directly applied / saved significant effort`,
|
|
294
|
+
args: {
|
|
295
|
+
task_id: tool.schema.string().describe("The task_id returned by memory_start_task"),
|
|
296
|
+
tokens_used: tool.schema.number().describe("Total tokens used during this task"),
|
|
297
|
+
tool_calls: tool.schema.number().describe("Total tool calls made during this task"),
|
|
298
|
+
errors: tool.schema.number().describe("Number of errors encountered (failed commands, test failures, etc.)"),
|
|
299
|
+
user_corrections: tool.schema.number().describe("Number of times the user corrected you or denied a tool call"),
|
|
300
|
+
completed: tool.schema.boolean().describe("Whether the task was completed successfully"),
|
|
301
|
+
self_report: tool.schema.array(tool.schema.object({
|
|
302
|
+
memory_id: tool.schema.string(),
|
|
303
|
+
score: tool.schema.number().min(0).max(3)
|
|
304
|
+
})).optional().describe("Rate each retrieved memory: 0=ignored, 1=glanced, 2=useful, 3=directly applied")
|
|
305
|
+
},
|
|
306
|
+
async execute(args) {
|
|
307
|
+
try {
|
|
308
|
+
const store = await storeManager.getStore();
|
|
309
|
+
await store.endTask(args.task_id, {
|
|
310
|
+
tokensUsed: args.tokens_used,
|
|
311
|
+
toolCalls: args.tool_calls,
|
|
312
|
+
errors: args.errors,
|
|
313
|
+
userCorrections: args.user_corrections,
|
|
314
|
+
completed: args.completed,
|
|
315
|
+
selfReport: args.self_report?.map((s) => ({
|
|
316
|
+
memoryId: s.memory_id,
|
|
317
|
+
score: s.score
|
|
318
|
+
}))
|
|
319
|
+
});
|
|
320
|
+
const decayResult = await store.decay();
|
|
321
|
+
let text = "Task ended. Score recorded and memory weights updated.";
|
|
322
|
+
if (decayResult.deleted > 0) {
|
|
323
|
+
text += `
|
|
324
|
+
${decayResult.deleted} stale memories cleaned up.`;
|
|
51
325
|
}
|
|
326
|
+
return text;
|
|
327
|
+
} catch (e) {
|
|
328
|
+
return `Error: ${e.message}`;
|
|
52
329
|
}
|
|
53
330
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
331
|
+
}),
|
|
332
|
+
memory_contradict: tool({
|
|
333
|
+
description: `Flag a retrieved memory as incorrect and remove it. Call this when a memory is factually wrong or led you down the wrong path.
|
|
334
|
+
|
|
335
|
+
Optionally provide a correction — the correct information that should replace the bad memory.
|
|
336
|
+
|
|
337
|
+
Example: You retrieve a memory saying "config is in /etc/shadow" but discover it's actually in src/config/auth.toml. Call this with the memory_id and correction="Config is in src/config/auth.toml, not /etc/shadow".`,
|
|
338
|
+
args: {
|
|
339
|
+
memory_id: tool.schema.string().describe("The ID of the incorrect memory to remove"),
|
|
340
|
+
correction: tool.schema.string().optional().describe("What's actually true — stored as a new high-weight memory to replace the wrong one")
|
|
341
|
+
},
|
|
342
|
+
async execute(args) {
|
|
343
|
+
try {
|
|
344
|
+
const store = await storeManager.getStore();
|
|
345
|
+
const result = await store.contradictMemory(args.memory_id, args.correction);
|
|
346
|
+
if (!result.deleted) {
|
|
347
|
+
return `Memory ${args.memory_id} not found.`;
|
|
348
|
+
}
|
|
349
|
+
let text = "Incorrect memory deleted.";
|
|
350
|
+
if (result.correctionId) {
|
|
351
|
+
text += ` Correction saved (id: ${result.correctionId}).`;
|
|
352
|
+
}
|
|
353
|
+
return text;
|
|
354
|
+
} catch (e) {
|
|
355
|
+
return `Error: ${e.message}`;
|
|
356
|
+
}
|
|
62
357
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
358
|
+
}),
|
|
359
|
+
memory_status: tool({
|
|
360
|
+
description: "Show memory system statistics: total memories stored, memory health, task history. Use this to check that the memory system is working.",
|
|
361
|
+
args: {},
|
|
362
|
+
async execute() {
|
|
363
|
+
try {
|
|
364
|
+
const store = await storeManager.getStore();
|
|
365
|
+
const stats = await store.getStats();
|
|
366
|
+
const lines = [
|
|
367
|
+
"memelord status:",
|
|
368
|
+
` Memories stored: ${stats.totalMemories}`,
|
|
369
|
+
` Tasks completed: ${stats.taskCount}`,
|
|
370
|
+
` Avg task score: ${stats.avgTaskScore.toFixed(3)}`
|
|
371
|
+
];
|
|
372
|
+
if (stats.topMemories.length > 0) {
|
|
373
|
+
lines.push(`
|
|
374
|
+
Top memories by weight:`);
|
|
375
|
+
for (const mem of stats.topMemories.slice(0, 5)) {
|
|
376
|
+
const preview = mem.content.length > 80 ? mem.content.slice(0, 80) + "..." : mem.content;
|
|
377
|
+
lines.push(` [w=${mem.weight.toFixed(2)}, used=${mem.retrievalCount}x] ${preview}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return lines.join(`
|
|
66
381
|
`);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
return `Error: ${e.message}`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/hooks.ts
|
|
391
|
+
function extractToolSequences(messages) {
|
|
392
|
+
const sequence = [];
|
|
393
|
+
for (const { parts } of messages) {
|
|
394
|
+
for (const part of parts) {
|
|
395
|
+
if (part.type !== "tool")
|
|
396
|
+
continue;
|
|
397
|
+
if (part.state.status === "completed") {
|
|
398
|
+
const output = part.state.output ?? "";
|
|
399
|
+
const hasError = output.includes("Error:") || output.includes("ENOENT") || output.includes("command not found");
|
|
400
|
+
sequence.push({
|
|
401
|
+
tool: part.tool,
|
|
402
|
+
input: part.state.input,
|
|
403
|
+
failed: hasError
|
|
404
|
+
});
|
|
405
|
+
} else if (part.state.status === "error") {
|
|
406
|
+
sequence.push({
|
|
407
|
+
tool: part.tool,
|
|
408
|
+
input: part.state.input,
|
|
409
|
+
failed: true
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
67
413
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
414
|
+
return sequence;
|
|
415
|
+
}
|
|
416
|
+
function detectCorrections(sequence) {
|
|
417
|
+
const corrections = [];
|
|
418
|
+
for (let i = 0;i < sequence.length - 1; i++) {
|
|
419
|
+
if (!sequence[i].failed)
|
|
420
|
+
continue;
|
|
421
|
+
for (let j = i + 1;j < Math.min(i + 4, sequence.length); j++) {
|
|
422
|
+
if (sequence[j].tool === sequence[i].tool && !sequence[j].failed) {
|
|
423
|
+
const failedInput = typeof sequence[i].input === "string" ? sequence[i].input : JSON.stringify(sequence[i].input).slice(0, 200);
|
|
424
|
+
const succeededInput = typeof sequence[j].input === "string" ? sequence[j].input : JSON.stringify(sequence[j].input).slice(0, 200);
|
|
425
|
+
if (failedInput !== succeededInput) {
|
|
426
|
+
corrections.push({
|
|
427
|
+
failedTool: sequence[i].tool,
|
|
428
|
+
failedInput,
|
|
429
|
+
succeededTool: sequence[j].tool,
|
|
430
|
+
succeededInput
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return corrections;
|
|
438
|
+
}
|
|
439
|
+
function sumTokens(messages) {
|
|
440
|
+
let total = 0;
|
|
441
|
+
for (const { info } of messages) {
|
|
442
|
+
if (info.role === "assistant") {
|
|
443
|
+
const a = info;
|
|
444
|
+
total += a.tokens.input;
|
|
445
|
+
total += a.tokens.output;
|
|
446
|
+
total += a.tokens.cache.write;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return total;
|
|
450
|
+
}
|
|
451
|
+
function extractTextBlocks(messages) {
|
|
452
|
+
const texts = [];
|
|
453
|
+
for (const { info, parts } of messages) {
|
|
454
|
+
if (info.role !== "assistant")
|
|
455
|
+
continue;
|
|
456
|
+
for (const part of parts) {
|
|
457
|
+
if (part.type === "text" && typeof part.text === "string" && part.text.length > 80) {
|
|
458
|
+
texts.push(part.text);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return texts;
|
|
463
|
+
}
|
|
464
|
+
function isToolFailure(output, metadata) {
|
|
465
|
+
return metadata != null && typeof metadata.exitCode === "number" && metadata.exitCode !== 0 || typeof output === "string" && (output.startsWith("Error:") || output.startsWith("error:") || output.includes("ENOENT") || output.includes("command not found") || output.includes("No such file") || output.includes("Permission denied"));
|
|
466
|
+
}
|
|
467
|
+
async function onSessionCreated(sessionId, client, storeManager) {
|
|
468
|
+
storeManager.setCurrentSessionId(sessionId);
|
|
469
|
+
const store = await storeManager.getStore();
|
|
470
|
+
try {
|
|
471
|
+
const memories = await store.getTopByWeight(5);
|
|
472
|
+
storeManager.setSessionState(sessionId, {
|
|
473
|
+
sessionId,
|
|
474
|
+
startedAt: Math.floor(Date.now() / 1000),
|
|
475
|
+
injectedMemoryIds: memories.map((m) => m.id)
|
|
75
476
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
parts: [{ type: "text", text: context }]
|
|
87
|
-
}
|
|
88
|
-
});
|
|
477
|
+
let context = "";
|
|
478
|
+
if (memories.length > 0) {
|
|
479
|
+
context += `# Memories from past sessions
|
|
480
|
+
|
|
481
|
+
`;
|
|
482
|
+
for (const mem of memories) {
|
|
483
|
+
context += `[${mem.category}] (id: ${mem.id}, weight: ${mem.weight.toFixed(2)})
|
|
484
|
+
${mem.content}
|
|
485
|
+
|
|
486
|
+
`;
|
|
89
487
|
}
|
|
90
|
-
}
|
|
488
|
+
}
|
|
489
|
+
context += `# Memory system instructions
|
|
490
|
+
|
|
491
|
+
You have a persistent memory system available via tools. Use it:
|
|
492
|
+
|
|
493
|
+
1. At the START of every task, call memory_start_task with the user's request. This retrieves task-relevant memories using vector search (more precise than the weight-based ones above).
|
|
494
|
+
|
|
495
|
+
2. When you self-correct (tried something that failed, then found the right approach), call memory_report with type "correction".
|
|
496
|
+
|
|
497
|
+
3. When the user corrects you or shares project knowledge, call memory_report with type "user_input". The user should never have to tell you the same thing twice.
|
|
498
|
+
|
|
499
|
+
4. When you discover something useful about the codebase (key file locations, architecture patterns, build/test conventions), call memory_report with type "insight". This saves future sessions from re-exploring the same codebase.
|
|
500
|
+
|
|
501
|
+
5. IMPORTANT — Before finishing a task, review the memories above against what you actually found. If any memory contains incorrect information (wrong file paths, wrong function names, wrong explanations), you MUST call memory_contradict with its id to remove it. Provide the correct information so future sessions get it right. Bad memories poison every future session if not removed.
|
|
502
|
+
|
|
503
|
+
6. When you finish a task, call memory_end_task with outcome metrics and rate each retrieved memory (0=ignored, 1=glanced, 2=useful, 3=directly applied).`;
|
|
504
|
+
await client.session.prompt({
|
|
505
|
+
path: { id: sessionId },
|
|
506
|
+
body: {
|
|
507
|
+
noReply: true,
|
|
508
|
+
parts: [{ type: "text", text: context }]
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
} catch (e) {
|
|
512
|
+
console.error(`memelord SessionStart error: ${e.message}`);
|
|
91
513
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
514
|
+
}
|
|
515
|
+
function onToolAfter(sessionId, toolName, toolInput, toolOutput, toolMetadata, storeManager) {
|
|
516
|
+
const meta = toolMetadata;
|
|
517
|
+
if (!isToolFailure(toolOutput, meta))
|
|
518
|
+
return;
|
|
519
|
+
const errorSummary = toolOutput.slice(0, 500);
|
|
520
|
+
storeManager.appendFailure(sessionId, {
|
|
521
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
522
|
+
toolName,
|
|
523
|
+
toolInput,
|
|
524
|
+
errorSummary
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async function onSessionIdle(sessionId, client, storeManager) {
|
|
528
|
+
const store = await storeManager.getStore();
|
|
529
|
+
try {
|
|
530
|
+
let correctionsFound = 0;
|
|
531
|
+
let discoveryStored = false;
|
|
532
|
+
let messages = [];
|
|
99
533
|
try {
|
|
100
534
|
const response = await client.session.messages({
|
|
101
535
|
path: { id: sessionId }
|
|
102
536
|
});
|
|
103
|
-
|
|
104
|
-
if (messages && messages.length > 0) {
|
|
105
|
-
const transcript = convertTranscript(messages);
|
|
106
|
-
const tmpFile = join(tmpdir(), `memelord-transcript-${sessionId}.jsonl`);
|
|
107
|
-
writeFileSync(tmpFile, transcript);
|
|
108
|
-
stdinData.transcript_path = tmpFile;
|
|
109
|
-
await callHook("stop", stdinData);
|
|
110
|
-
try {
|
|
111
|
-
unlinkSync(tmpFile);
|
|
112
|
-
} catch {}
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
537
|
+
messages = response.data ?? [];
|
|
115
538
|
} catch {}
|
|
116
|
-
|
|
539
|
+
if (messages.length > 0) {
|
|
540
|
+
const sequence = extractToolSequences(messages);
|
|
541
|
+
const corrections = detectCorrections(sequence);
|
|
542
|
+
for (const c of corrections) {
|
|
543
|
+
const content = `Auto-detected correction with ${c.failedTool}:
|
|
544
|
+
|
|
545
|
+
Failed approach: ${c.failedInput}
|
|
546
|
+
Working approach: ${c.succeededInput}`;
|
|
547
|
+
await store.insertRawMemory(content, "correction", 1.5);
|
|
548
|
+
correctionsFound++;
|
|
549
|
+
}
|
|
550
|
+
const totalTokens = sumTokens(messages);
|
|
551
|
+
if (totalTokens >= 50000) {
|
|
552
|
+
const exploration = { reads: 0, searches: 0, edits: 0 };
|
|
553
|
+
for (const s of sequence) {
|
|
554
|
+
const t = s.tool;
|
|
555
|
+
if (t === "Read" || t === "read" || t.includes("cachebro_read_file") || t.includes("cachebro_read_files")) {
|
|
556
|
+
exploration.reads++;
|
|
557
|
+
} else if (t === "Grep" || t === "grep" || t === "Glob" || t === "glob" || t === "LSP" || t === "lsp") {
|
|
558
|
+
exploration.searches++;
|
|
559
|
+
} else if (t === "Edit" || t === "edit" || t === "Write" || t === "write") {
|
|
560
|
+
exploration.edits++;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const ratio = (exploration.reads + exploration.searches) / Math.max(exploration.reads + exploration.searches + exploration.edits, 1);
|
|
564
|
+
if (ratio > 0.5) {
|
|
565
|
+
const texts = extractTextBlocks(messages);
|
|
566
|
+
if (texts.length > 0) {
|
|
567
|
+
const sorted = [...texts].sort((a, b) => b.length - a.length);
|
|
568
|
+
const combined = new Set([
|
|
569
|
+
...sorted.slice(0, 5),
|
|
570
|
+
...texts.slice(-2)
|
|
571
|
+
]);
|
|
572
|
+
const ordered = texts.filter((t) => combined.has(t));
|
|
573
|
+
const summary = ordered.map((t) => t.slice(0, 500)).join(`
|
|
574
|
+
|
|
575
|
+
`).slice(0, 2000);
|
|
576
|
+
if (summary.length >= 100) {
|
|
577
|
+
await store.insertRawMemory(`[Discovery after ${Math.round(totalTokens / 1000)}k tokens, ${sequence.length} tool calls]
|
|
578
|
+
|
|
579
|
+
${summary}`, "discovery", 1);
|
|
580
|
+
discoveryStored = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const totalTokens2 = sumTokens(messages);
|
|
586
|
+
if (totalTokens2 >= 20000) {
|
|
587
|
+
const session = storeManager.getSessionState(sessionId);
|
|
588
|
+
const injectedIds = session?.injectedMemoryIds ?? [];
|
|
589
|
+
if (injectedIds.length > 0) {
|
|
590
|
+
let penalized = 0;
|
|
591
|
+
for (const id of injectedIds) {
|
|
592
|
+
await store.penalizeMemory(id, 0.999);
|
|
593
|
+
penalized++;
|
|
594
|
+
}
|
|
595
|
+
if (penalized > 0) {
|
|
596
|
+
console.error(`memelord: penalized ${penalized} injected memories (session used ${Math.round(totalTokens2 / 1000)}k tokens)`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const failures = storeManager.getFailures(sessionId);
|
|
602
|
+
if (failures.length > 0) {
|
|
603
|
+
const toolFailCounts = new Map;
|
|
604
|
+
for (const f of failures) {
|
|
605
|
+
toolFailCounts.set(f.toolName, (toolFailCounts.get(f.toolName) ?? 0) + 1);
|
|
606
|
+
}
|
|
607
|
+
for (const [toolName, count] of toolFailCounts) {
|
|
608
|
+
if (count >= 3) {
|
|
609
|
+
const examples = failures.filter((f) => f.toolName === toolName).slice(0, 2).map((f) => f.errorSummary.slice(0, 100)).join("; ");
|
|
610
|
+
await store.insertRawMemory(`Repeated failures with ${toolName} (${count}x in session): ${examples}`, "correction", 1);
|
|
611
|
+
correctionsFound++;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (correctionsFound > 0) {
|
|
616
|
+
console.error(`memelord: stored ${correctionsFound} auto-detected corrections`);
|
|
617
|
+
}
|
|
618
|
+
if (discoveryStored) {
|
|
619
|
+
console.error("memelord: stored 1 discovery from high-token exploration");
|
|
620
|
+
}
|
|
621
|
+
} catch (e) {
|
|
622
|
+
console.error(`memelord Stop error: ${e.message}`);
|
|
117
623
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
624
|
+
}
|
|
625
|
+
async function onSessionDeleted(sessionId, storeManager) {
|
|
626
|
+
try {
|
|
627
|
+
const store = await storeManager.getLiveStore();
|
|
628
|
+
const embedded = await store.embedPending();
|
|
629
|
+
if (embedded > 0) {
|
|
630
|
+
console.error(`memelord: embedded ${embedded} pending memories`);
|
|
631
|
+
}
|
|
632
|
+
const decayResult = await store.decay();
|
|
633
|
+
if (decayResult.deleted > 0) {
|
|
634
|
+
console.error(`memelord: cleaned up ${decayResult.deleted} stale memories`);
|
|
635
|
+
}
|
|
636
|
+
storeManager.cleanupSession(sessionId);
|
|
637
|
+
} catch (e) {
|
|
638
|
+
console.error(`memelord SessionEnd error: ${e.message}`);
|
|
125
639
|
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/index.ts
|
|
643
|
+
var MemelordPlugin = async ({ client, worktree, $ }) => {
|
|
644
|
+
const storeManager = await createStoreManager(worktree, $);
|
|
126
645
|
return {
|
|
646
|
+
tool: createTools(storeManager),
|
|
127
647
|
"tool.execute.after": async (input, output) => {
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
if (!currentSessionId)
|
|
131
|
-
return;
|
|
132
|
-
callHook("post-tool-use", {
|
|
133
|
-
session_id: currentSessionId,
|
|
134
|
-
cwd,
|
|
135
|
-
tool_name: input.tool,
|
|
136
|
-
tool_input: input.args,
|
|
137
|
-
tool_response: output.output ?? ""
|
|
138
|
-
}).catch(() => {});
|
|
648
|
+
onToolAfter(input.sessionID, input.tool, input.args, output.output ?? "", output.metadata, storeManager);
|
|
139
649
|
},
|
|
140
650
|
event: async (input) => {
|
|
141
651
|
const { event } = input;
|
|
142
652
|
if (event.type === "session.created") {
|
|
143
|
-
await onSessionCreated(event.properties.info.id);
|
|
653
|
+
await onSessionCreated(event.properties.info.id, client, storeManager);
|
|
144
654
|
} else if (event.type === "session.idle") {
|
|
145
|
-
onSessionIdle(event.properties.sessionID).catch(() => {});
|
|
655
|
+
onSessionIdle(event.properties.sessionID, client, storeManager).catch(() => {});
|
|
146
656
|
} else if (event.type === "session.deleted") {
|
|
147
|
-
await onSessionDeleted(event.properties.info.id);
|
|
657
|
+
await onSessionDeleted(event.properties.info.id, storeManager);
|
|
148
658
|
}
|
|
149
659
|
}
|
|
150
660
|
};
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store management: DB path resolution, store factory, and session state on disk.
|
|
3
|
+
*
|
|
4
|
+
* Databases live at ~/.config/memelord/projects/<project-key>/memory.db
|
|
5
|
+
* where project-key is derived from the git remote URL (stable across worktrees).
|
|
6
|
+
*
|
|
7
|
+
* Session state (injected memory IDs, failures) is stored alongside the DB
|
|
8
|
+
* in a sessions/ subdirectory, surviving OpenCode restarts.
|
|
9
|
+
*/
|
|
10
|
+
import { type MemoryStore } from 'memelord';
|
|
11
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
12
|
+
type BunShell = PluginInput['$'];
|
|
13
|
+
/**
|
|
14
|
+
* Derive a stable project key from the git remote URL.
|
|
15
|
+
* Falls back to hashing the worktree path if no remote is available.
|
|
16
|
+
* Results are cached so the hash is only computed once per worktree.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getProjectKey(worktree: string, $: BunShell): Promise<string>;
|
|
19
|
+
export interface SessionState {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
startedAt: number;
|
|
22
|
+
injectedMemoryIds: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface ToolFailure {
|
|
25
|
+
timestamp: number;
|
|
26
|
+
toolName: string;
|
|
27
|
+
toolInput: unknown;
|
|
28
|
+
errorSummary: string;
|
|
29
|
+
}
|
|
30
|
+
export interface StoreManager {
|
|
31
|
+
/** Light store with dummy embedder — for hooks (fast, no model loading). */
|
|
32
|
+
getStore(): Promise<MemoryStore>;
|
|
33
|
+
/** Live store with real embedder — for tools + session.deleted (lazy-loads model). */
|
|
34
|
+
getLiveStore(): Promise<MemoryStore>;
|
|
35
|
+
/** Set the current session ID (called on session.created). */
|
|
36
|
+
setCurrentSessionId(sessionId: string): void;
|
|
37
|
+
getSessionState(sessionId: string): SessionState | null;
|
|
38
|
+
setSessionState(sessionId: string, state: SessionState): void;
|
|
39
|
+
getFailures(sessionId: string): ToolFailure[];
|
|
40
|
+
appendFailure(sessionId: string, failure: ToolFailure): void;
|
|
41
|
+
cleanupSession(sessionId: string): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createStoreManager(worktree: string, $: BunShell): Promise<StoreManager>;
|
|
44
|
+
export {};
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { StoreManager } from './store.js';
|
|
2
|
+
export declare function createTools(storeManager: StoreManager): {
|
|
3
|
+
memory_start_task: {
|
|
4
|
+
description: string;
|
|
5
|
+
args: {
|
|
6
|
+
description: import("zod").ZodString;
|
|
7
|
+
};
|
|
8
|
+
execute(args: {
|
|
9
|
+
description: string;
|
|
10
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
11
|
+
};
|
|
12
|
+
memory_report: {
|
|
13
|
+
description: string;
|
|
14
|
+
args: {
|
|
15
|
+
type: import("zod").ZodEnum<{
|
|
16
|
+
correction: "correction";
|
|
17
|
+
insight: "insight";
|
|
18
|
+
user_input: "user_input";
|
|
19
|
+
}>;
|
|
20
|
+
lesson: import("zod").ZodString;
|
|
21
|
+
what_failed: import("zod").ZodOptional<import("zod").ZodString>;
|
|
22
|
+
what_worked: import("zod").ZodOptional<import("zod").ZodString>;
|
|
23
|
+
tokens_wasted: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
24
|
+
tools_wasted: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
25
|
+
source: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
26
|
+
user_input: "user_input";
|
|
27
|
+
user_denial: "user_denial";
|
|
28
|
+
user_correction: "user_correction";
|
|
29
|
+
}>>;
|
|
30
|
+
};
|
|
31
|
+
execute(args: {
|
|
32
|
+
type: "correction" | "insight" | "user_input";
|
|
33
|
+
lesson: string;
|
|
34
|
+
what_failed?: string | undefined;
|
|
35
|
+
what_worked?: string | undefined;
|
|
36
|
+
tokens_wasted?: number | undefined;
|
|
37
|
+
tools_wasted?: number | undefined;
|
|
38
|
+
source?: "user_input" | "user_denial" | "user_correction" | undefined;
|
|
39
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
40
|
+
};
|
|
41
|
+
memory_end_task: {
|
|
42
|
+
description: string;
|
|
43
|
+
args: {
|
|
44
|
+
task_id: import("zod").ZodString;
|
|
45
|
+
tokens_used: import("zod").ZodNumber;
|
|
46
|
+
tool_calls: import("zod").ZodNumber;
|
|
47
|
+
errors: import("zod").ZodNumber;
|
|
48
|
+
user_corrections: import("zod").ZodNumber;
|
|
49
|
+
completed: import("zod").ZodBoolean;
|
|
50
|
+
self_report: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
|
|
51
|
+
memory_id: import("zod").ZodString;
|
|
52
|
+
score: import("zod").ZodNumber;
|
|
53
|
+
}, import("zod/v4/core").$strip>>>;
|
|
54
|
+
};
|
|
55
|
+
execute(args: {
|
|
56
|
+
task_id: string;
|
|
57
|
+
tokens_used: number;
|
|
58
|
+
tool_calls: number;
|
|
59
|
+
errors: number;
|
|
60
|
+
user_corrections: number;
|
|
61
|
+
completed: boolean;
|
|
62
|
+
self_report?: {
|
|
63
|
+
memory_id: string;
|
|
64
|
+
score: number;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
67
|
+
};
|
|
68
|
+
memory_contradict: {
|
|
69
|
+
description: string;
|
|
70
|
+
args: {
|
|
71
|
+
memory_id: import("zod").ZodString;
|
|
72
|
+
correction: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
|
+
};
|
|
74
|
+
execute(args: {
|
|
75
|
+
memory_id: string;
|
|
76
|
+
correction?: string | undefined;
|
|
77
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
78
|
+
};
|
|
79
|
+
memory_status: {
|
|
80
|
+
description: string;
|
|
81
|
+
args: {};
|
|
82
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
83
|
+
};
|
|
84
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-memelord",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "OpenCode plugin for memelord
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin for memelord — persistent memory for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target node && tsc --emitDeclarationOnly",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
9
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node --external memelord --external @opencode-ai/plugin --external @opencode-ai/sdk --external @huggingface/transformers --external zod && tsc --emitDeclarationOnly",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"format": "oxfmt --write",
|
|
12
|
+
"format:check": "oxfmt --check"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [
|
|
13
15
|
"opencode",
|
|
@@ -24,11 +26,13 @@
|
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
26
28
|
"@opencode-ai/plugin": "^1.2.10",
|
|
29
|
+
"@huggingface/transformers": "^3.0.0",
|
|
27
30
|
"memelord": "^0.1.4"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
33
|
"@opencode-ai/sdk": "^1.2.10",
|
|
31
34
|
"@types/bun": "latest",
|
|
35
|
+
"oxfmt": "^0.34.0",
|
|
32
36
|
"typescript": "^5.0.0"
|
|
33
37
|
},
|
|
34
38
|
"opencode": {
|