opencode-mempalace-persistence 2.2.0 → 2.4.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 +5 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +63 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,6 +179,11 @@ The model responds
|
|
|
179
179
|
|
|
180
180
|
Session goes idle / process exits
|
|
181
181
|
→ Background mine of everything new since last sync
|
|
182
|
+
→ TUI toast confirms what was mined (disable with `"toasts": false`)
|
|
183
|
+
|
|
184
|
+
Every MemPalace call — plugin searches, model MCP calls (search, diary,
|
|
185
|
+
KG) — also raises a short TUI toast with what was asked and a result
|
|
186
|
+
preview, so background memory activity is always visible.
|
|
182
187
|
|
|
183
188
|
Compaction starts
|
|
184
189
|
→ [MemPalace Pre-Compact Emergency Save]: model files everything first
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const _default: () => Promise<{
|
|
1
|
+
declare const _default: ({ client }: any) => Promise<{
|
|
2
2
|
"chat.message": (input: {
|
|
3
3
|
sessionID: string;
|
|
4
4
|
agent?: string;
|
|
@@ -24,6 +24,16 @@ declare const _default: () => Promise<{
|
|
|
24
24
|
context: string[];
|
|
25
25
|
prompt?: string;
|
|
26
26
|
}) => Promise<void>;
|
|
27
|
+
"tool.execute.after": (input: {
|
|
28
|
+
tool: string;
|
|
29
|
+
sessionID: string;
|
|
30
|
+
callID: string;
|
|
31
|
+
args: any;
|
|
32
|
+
}, output: {
|
|
33
|
+
title: string;
|
|
34
|
+
output: string;
|
|
35
|
+
metadata: any;
|
|
36
|
+
}) => Promise<void>;
|
|
27
37
|
event: ({ event }: any) => Promise<void>;
|
|
28
38
|
}>;
|
|
29
39
|
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,29 @@ function errLog(msg) {
|
|
|
46
46
|
log("ERROR: " + msg);
|
|
47
47
|
hookLog("ERROR: " + msg);
|
|
48
48
|
}
|
|
49
|
+
function toastsEnabled() {
|
|
50
|
+
try {
|
|
51
|
+
const raw = readFileSync(PLUGIN_CONFIG, "utf-8");
|
|
52
|
+
const v = JSON.parse(raw)?.toasts;
|
|
53
|
+
if (v === false)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
// TUI toast client (set by the factory). Fire-and-forget: headless runs
|
|
60
|
+
// (`opencode run`, no TUI attached) must never break on this.
|
|
61
|
+
let tuiClient = null;
|
|
62
|
+
function toast(variant, title, message) {
|
|
63
|
+
if (!toastsEnabled() || !tuiClient?.tui?.showToast)
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
const p = tuiClient.tui.showToast({ body: { title, message, variant, duration: 5000 } });
|
|
67
|
+
if (p && typeof p.catch === "function")
|
|
68
|
+
p.catch(() => { });
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
}
|
|
49
72
|
// Probe for a working Python interpreter at startup instead of hardcoding
|
|
50
73
|
// one installer layout (pipx vs uv tool vs system). runPython only needs
|
|
51
74
|
// stdlib (sqlite3/json), so any python3 works. Priority: explicit env
|
|
@@ -228,14 +251,36 @@ function mempalaceSearch(query) {
|
|
|
228
251
|
encoding: "utf-8",
|
|
229
252
|
timeout: 15000,
|
|
230
253
|
}).trim();
|
|
231
|
-
if (!out || out.includes("No results"))
|
|
254
|
+
if (!out || out.includes("No results")) {
|
|
255
|
+
toast("info", "MemPalace", `search "${query.slice(0, 50)}" → no results`);
|
|
232
256
|
return "";
|
|
257
|
+
}
|
|
258
|
+
const n = (out.match(/\n\s*\[\d+\]/g) || []).length || 1;
|
|
259
|
+
toast("info", "MemPalace", `search "${query.slice(0, 50)}" → ${n} result(s)`);
|
|
233
260
|
return out.slice(0, MAX_INJECT_CHARS);
|
|
234
261
|
}
|
|
235
262
|
catch {
|
|
236
263
|
return "";
|
|
237
264
|
}
|
|
238
265
|
}
|
|
266
|
+
// TUI visibility for model-driven MCP calls (skill recall, diary, KG):
|
|
267
|
+
// the plugin can't see inside the agent, but it sees every tool result.
|
|
268
|
+
function isMemPalaceTool(name) {
|
|
269
|
+
return typeof name === "string" && name.toLowerCase().includes("mempalace");
|
|
270
|
+
}
|
|
271
|
+
function summarizeToolCall(tool, args, out) {
|
|
272
|
+
const short = tool.replace(/^mcp_+/, "").replace(/^mempalace_mempalace_/, "").replace(/^mempalace_/, "");
|
|
273
|
+
let asked = "";
|
|
274
|
+
try {
|
|
275
|
+
const a = typeof args === "string" ? args : JSON.stringify(args || {});
|
|
276
|
+
asked = a.replace(/\s+/g, " ").slice(0, 60);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
asked = "";
|
|
280
|
+
}
|
|
281
|
+
const answered = (out || "").replace(/\s+/g, " ").slice(0, 80) || "(empty)";
|
|
282
|
+
return `${short} · asked: ${asked} → ${answered}`.slice(0, 220);
|
|
283
|
+
}
|
|
239
284
|
function getLastSync() {
|
|
240
285
|
if (!existsSync(STATE_FILE))
|
|
241
286
|
return 0;
|
|
@@ -434,6 +479,8 @@ function doDbSync() {
|
|
|
434
479
|
markSynced(now);
|
|
435
480
|
cleanupExport(wings);
|
|
436
481
|
log("mine done");
|
|
482
|
+
const names = [...wings.keys()].join(", ");
|
|
483
|
+
toast("success", "MemPalace", `mined ${wingCount(wings)} session(s) → ${names}`);
|
|
437
484
|
return;
|
|
438
485
|
}
|
|
439
486
|
const [wing, files] = entries[i];
|
|
@@ -453,6 +500,7 @@ function doDbSync() {
|
|
|
453
500
|
if (err) {
|
|
454
501
|
miningLock = false;
|
|
455
502
|
errLog(`mine err (${wing}): ${err.message}`);
|
|
503
|
+
toast("error", "MemPalace", `mine failed (${wing}): ${err.message.slice(0, 120)}`);
|
|
456
504
|
return;
|
|
457
505
|
}
|
|
458
506
|
log(`mined wing ${wing} (${files.length} sessions)`);
|
|
@@ -501,7 +549,8 @@ function exitSync() {
|
|
|
501
549
|
errLog("exit save err: " + String(e));
|
|
502
550
|
}
|
|
503
551
|
}
|
|
504
|
-
export default (async () => {
|
|
552
|
+
export default (async ({ client }) => {
|
|
553
|
+
tuiClient = client || null;
|
|
505
554
|
mkdirSync(OUT_DIR, { recursive: true, mode: 0o700 });
|
|
506
555
|
mkdirSync(HOOK_STATE_DIR, { recursive: true });
|
|
507
556
|
const autoInject = isAutoInjectEnabled();
|
|
@@ -547,6 +596,7 @@ export default (async () => {
|
|
|
547
596
|
c.lastCheckpoint = boundary;
|
|
548
597
|
pendingCheckpoint = { sessionID, count: c.humanMsgs };
|
|
549
598
|
hookLog(`session ${sessionID}: ${c.humanMsgs} human msgs — checkpoint armed`);
|
|
599
|
+
toast("info", "MemPalace", `checkpoint armed (~${c.humanMsgs} msgs): the model will file memories now`);
|
|
550
600
|
}
|
|
551
601
|
counters[sessionID] = c;
|
|
552
602
|
persistCounters(counters);
|
|
@@ -624,6 +674,17 @@ export default (async () => {
|
|
|
624
674
|
output.context.push(`[MemPalace Rescue — core memory, must survive compaction]\n${rescue.join("\n\n")}`);
|
|
625
675
|
}
|
|
626
676
|
},
|
|
677
|
+
"tool.execute.after": async (input, output) => {
|
|
678
|
+
try {
|
|
679
|
+
const name = input?.tool || "";
|
|
680
|
+
if (!isMemPalaceTool(name))
|
|
681
|
+
return;
|
|
682
|
+
const summary = summarizeToolCall(name, input?.args, output?.output || "");
|
|
683
|
+
log(`tool: ${summary}`);
|
|
684
|
+
toast("info", "MemPalace", summary);
|
|
685
|
+
}
|
|
686
|
+
catch { }
|
|
687
|
+
},
|
|
627
688
|
event: async ({ event }) => {
|
|
628
689
|
if (event?.type === "session.idle" || event?.type === "session.deleted") {
|
|
629
690
|
log(`${event.type} - queue sync`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mempalace-persistence",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "OpenCode plugin — auto-sync conversations to MemPalace memory in real-time. No forced wings, KG extraction via MCP tools.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|