opencode-mempalace-persistence 2.5.2 → 2.5.3
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 +2 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +76 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,7 +177,8 @@ The model responds
|
|
|
177
177
|
→ Model records new KG facts via MCP tools (only when something new emerged)
|
|
178
178
|
|
|
179
179
|
Session goes idle / process exits
|
|
180
|
-
→ Background mine of everything new since last sync
|
|
180
|
+
→ Background mine of everything new since last sync (per-wing cursors:
|
|
181
|
+
each wing advances independently, so one slow wing never stalls the rest)
|
|
181
182
|
→ TUI toast confirms what was mined (disable with `"toasts": false`)
|
|
182
183
|
|
|
183
184
|
Every MemPalace call — plugin searches, model MCP calls (search, diary,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
declare const _default: ({ client }: any) => Promise<{
|
|
2
|
+
tool: {
|
|
3
|
+
mempalace_sync: {
|
|
4
|
+
description: string;
|
|
5
|
+
args: {};
|
|
6
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
2
9
|
"chat.message": (input: {
|
|
3
10
|
sessionID: string;
|
|
4
11
|
agent?: string;
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { homedir } from "os";
|
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { tool } from "@opencode-ai/plugin";
|
|
7
8
|
const HOME = homedir();
|
|
8
9
|
const MEMPALACE_BIN = join(HOME, ".local/bin/mempalace");
|
|
9
10
|
const OPENCODE_DB = join(HOME, ".local/share/opencode/opencode.db");
|
|
@@ -161,7 +162,52 @@ function countPendingSuffix() {
|
|
|
161
162
|
const pending = countPendingFiles();
|
|
162
163
|
return pending > 0 ? `, ${pending} file(s) waiting to mine` : ", queue empty";
|
|
163
164
|
}
|
|
164
|
-
//
|
|
165
|
+
// On-demand sync snapshot for the /memory-toast command: same facts as
|
|
166
|
+
// the startup toast, plus live-mine detection. Fires a toast AND returns
|
|
167
|
+
// the text (visible in transcript too).
|
|
168
|
+
function liveMiners() {
|
|
169
|
+
const found = [];
|
|
170
|
+
let dir = [];
|
|
171
|
+
try {
|
|
172
|
+
dir = readdirSync(join(HOME, ".mempalace/locks"));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return found;
|
|
176
|
+
}
|
|
177
|
+
for (const f of dir) {
|
|
178
|
+
if (!f.endsWith(".lock"))
|
|
179
|
+
continue;
|
|
180
|
+
try {
|
|
181
|
+
const content = readFileSync(join(HOME, ".mempalace/locks", f), "utf-8");
|
|
182
|
+
const pid = parseInt((content.match(/(\d+)/) || [])[1] || "", 10);
|
|
183
|
+
if (!pid)
|
|
184
|
+
continue;
|
|
185
|
+
try {
|
|
186
|
+
process.kill(pid, 0);
|
|
187
|
+
found.push(`PID ${pid} (${f.replace("mine_palace_", "").replace(".lock", "").slice(0, 8)}…)`);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
function syncSnapshot() {
|
|
196
|
+
const st = readSyncState();
|
|
197
|
+
const pending = countPendingFiles();
|
|
198
|
+
const miners = liveMiners();
|
|
199
|
+
const agoMin = st.last_sync_ms > 0 ? Math.round((Date.now() - st.last_sync_ms) / 60000) : -1;
|
|
200
|
+
const syncAge = agoMin < 0 ? "never" : agoMin === 0 ? "<1 min ago" : `${agoMin} min ago`;
|
|
201
|
+
const mining = miners.length > 0 ? `mining NOW (${miners.join(", ")})` : "no mine running";
|
|
202
|
+
const text = `MemPalace sync — plugin v${pluginVersion()}\n` +
|
|
203
|
+
`Last sync: ${syncAge}\n` +
|
|
204
|
+
`Backlog: ${pending} file(s) waiting\n` +
|
|
205
|
+
`Status: ${mining}`;
|
|
206
|
+
const toastMsg = miners.length > 0
|
|
207
|
+
? `mining now (${miners.length}), ${pending} file(s) waiting, last sync ${syncAge}`
|
|
208
|
+
: `idle, ${pending} file(s) waiting, last sync ${syncAge}`;
|
|
209
|
+
return { text, toastMsg };
|
|
210
|
+
}
|
|
165
211
|
// (`opencode run`, no TUI attached) must never break on this.
|
|
166
212
|
let tuiClient = null;
|
|
167
213
|
// Throttle for routine skip notices (busy palace): at most one toast
|
|
@@ -487,11 +533,24 @@ for (mid, mts, mdata_raw) in rows:
|
|
|
487
533
|
# is created when a reply STARTS, parts stream in afterwards, and
|
|
488
534
|
# finish is set only on completion. Exporting mid-reply would
|
|
489
535
|
# snapshot partial parts while the cursor advances past the message
|
|
490
|
-
# timestamp — losing the rest of the reply forever. So
|
|
491
|
-
#
|
|
536
|
+
# timestamp — losing the rest of the reply forever. So unfinished
|
|
537
|
+
# replies are skipped and revisited next sync — BUT only while
|
|
538
|
+
# recently active. A reply with no new parts for a while is dead
|
|
539
|
+
# (killed session, crashed run): treating it as perpetually
|
|
540
|
+
# in-flight would pin the cursor forever (seen live: a stillborn
|
|
541
|
+
# message froze sync for 7h). Dead replies are exported as-is.
|
|
542
|
+
now_ms = int(__import__("time").time() * 1000)
|
|
543
|
+
STALE_PART_MS = 30 * 60 * 1000
|
|
544
|
+
STALE_EMPTY_MS = 10 * 60 * 1000
|
|
492
545
|
if role == "assistant" and not mdata.get("finish"):
|
|
493
|
-
|
|
494
|
-
|
|
546
|
+
max_part = db.execute("SELECT MAX(time_created) FROM part WHERE message_id = ?", (mid,)).fetchone()[0]
|
|
547
|
+
if max_part is None:
|
|
548
|
+
alive = (now_ms - mts) < STALE_EMPTY_MS
|
|
549
|
+
else:
|
|
550
|
+
alive = (now_ms - max_part) < STALE_PART_MS
|
|
551
|
+
if alive:
|
|
552
|
+
incomplete.append(mts)
|
|
553
|
+
continue
|
|
495
554
|
for (pdata_raw,) in db.execute("SELECT data FROM part WHERE message_id = ? ORDER BY time_created", (mid,)).fetchall():
|
|
496
555
|
try:
|
|
497
556
|
pdata = json.loads(pdata_raw)
|
|
@@ -802,6 +861,18 @@ export default (async ({ client }) => {
|
|
|
802
861
|
process.once("SIGHUP", onExit);
|
|
803
862
|
process.once("exit", onExit);
|
|
804
863
|
return {
|
|
864
|
+
tool: {
|
|
865
|
+
mempalace_sync: tool({
|
|
866
|
+
description: "Show live MemPalace sync state (backlog, last sync, running mines) as a TUI toast and text. Use when the user asks how mining is going.",
|
|
867
|
+
args: {},
|
|
868
|
+
async execute() {
|
|
869
|
+
const snap = syncSnapshot();
|
|
870
|
+
toast("info", "MemPalace", snap.toastMsg);
|
|
871
|
+
ilog("status", { via: "tool" });
|
|
872
|
+
return snap.text;
|
|
873
|
+
},
|
|
874
|
+
}),
|
|
875
|
+
},
|
|
805
876
|
"chat.message": async (input, output) => {
|
|
806
877
|
const role = output.message.role;
|
|
807
878
|
if (role !== "user")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mempalace-persistence",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
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",
|