opencode-mempalace-persistence 1.0.1 → 1.1.1
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 +81 -28
- package/dist/index.js +30 -107
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# opencode-mempalace-persistence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Community plugin** — not officially maintained by the MemPalace team. Fully open source, ~120 lines of TypeScript.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
An OpenCode plugin that automatically saves every conversation to MemPalace and uses stored memory to provide better, context-aware responses. Real-time, zero cron, zero external scripts.
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ An OpenCode plugin that automatically saves every conversation to MemPalace and
|
|
|
14
14
|
|---|---|
|
|
15
15
|
| Every session starts from scratch | The model knows who you are and what you've done |
|
|
16
16
|
| You repeat context each time | Memory is automatic |
|
|
17
|
-
|
|
|
17
|
+
| Model starts from scratch each time | Memory persists across sessions |
|
|
18
18
|
|
|
19
19
|
The model searches MemPalace on every question (via AGENTS.md + MCP), and the plugin saves every response. A perfect feedback loop.
|
|
20
20
|
|
|
@@ -39,11 +39,40 @@ Create `~/.config/opencode/AGENTS.md`:
|
|
|
39
39
|
```markdown
|
|
40
40
|
# Memory & Knowledge instructions
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
## CRITICAL: You MUST follow these steps BEFORE every response.
|
|
43
|
+
|
|
44
|
+
### Step 1 — Search MemPalace
|
|
45
|
+
Call `mempalace_mempalace_search` with the user's question or key topics as query. Get the top 5-10 most relevant memory drawers.
|
|
46
|
+
**This is mandatory. Never skip this step. No exceptions.** Every question — even seemingly trivial ones — may relate to past conversations, projects, or preferences you've forgotten. Searching costs ~nothing; skipping costs continuity.
|
|
47
|
+
|
|
48
|
+
### Step 2 — Query Knowledge Graph
|
|
49
|
+
Call `mempalace_mempalace_kg_query` for entity "user". Then filter the returned facts — keep only those whose text contains keywords from the user's question, so irrelevant facts are excluded.
|
|
50
|
+
|
|
51
|
+
### Step 3 — Use the context
|
|
52
|
+
Incorporate relevant memory and facts into your response. If the user's question relates to a past conversation, project, or decision, reference it naturally. If the search returned nothing useful, you are done — do NOT skip steps 1-2 just because you *expect* nothing.
|
|
53
|
+
|
|
54
|
+
### Naming reminder
|
|
55
|
+
All MemPalace tools use the prefix `mempalace_mempalace_*` (not `mempalace_*`). Examples:
|
|
56
|
+
- `mempalace_mempalace_search` (NOT `mempalace_search`)
|
|
57
|
+
- `mempalace_mempalace_kg_query` (NOT `mempalace_kg_query`)
|
|
58
|
+
- `mempalace_mempalace_add_drawer`
|
|
59
|
+
- `mempalace_mempalace_get_drawer`
|
|
60
|
+
- `mempalace_mempalace_list_wings`
|
|
61
|
+
If you ever catch yourself typing `mempalace_search` or `mempalace_kg_query`, STOP — the correct prefix is `mempalace_mempalace_`.
|
|
62
|
+
|
|
63
|
+
## Note
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
The KG stores structured facts like: decision (choices made), milestone (completed tasks), problem (issues encountered), preference (likes/dislikes), emotional (feelings). Use these to understand the user's history and context better.
|
|
66
|
+
|
|
67
|
+
### Knowledge Graph management
|
|
68
|
+
|
|
69
|
+
When you discover structured facts during conversation (decisions made, milestones reached, problems encountered, preferences expressed, emotional states), record them in the Knowledge Graph:
|
|
70
|
+
|
|
71
|
+
- **New facts**: Call `mempalace_mempalace_kg_add` with subject → predicate → object (e.g. `subject="user"`, `predicate="preference"`, `object="prefers TypeScript over Python"`)
|
|
72
|
+
- **Changed facts**: First call `mempalace_mempalace_kg_invalidate` on the old fact, then `mempalace_mempalace_kg_add` for the new one
|
|
73
|
+
- **Retrieval**: Call `mempalace_mempalace_kg_query` for entity "user" to see all known facts
|
|
74
|
+
|
|
75
|
+
This is optional but recommended — the more facts you record, the better the model understands the user's history and preferences.
|
|
47
76
|
```
|
|
48
77
|
|
|
49
78
|
### 3. Identity (who you are)
|
|
@@ -78,8 +107,10 @@ This is loaded automatically at session start via `instructions` in opencode.jso
|
|
|
78
107
|
### 5. MemPalace (if not already installed)
|
|
79
108
|
|
|
80
109
|
```bash
|
|
81
|
-
# Install
|
|
82
|
-
|
|
110
|
+
# Install (requires mempalace>=3.3.5 for HNSW corruption fix)
|
|
111
|
+
uv tool install "mempalace>=3.3.5"
|
|
112
|
+
# or
|
|
113
|
+
pipx install "mempalace>=3.3.5"
|
|
83
114
|
|
|
84
115
|
# Create palace
|
|
85
116
|
mempalace init ~/opencode-memory
|
|
@@ -102,8 +133,8 @@ You ask a question
|
|
|
102
133
|
|
|
103
134
|
The model responds
|
|
104
135
|
→ The opencode-mempalace-persistence plugin detects the response is complete
|
|
105
|
-
→ Saves the conversation to MemPalace
|
|
106
|
-
→
|
|
136
|
+
→ Saves the conversation to MemPalace (flat export, no hardcoded wings)
|
|
137
|
+
→ The model may record KG facts via MCP tools (optional, per-session)
|
|
107
138
|
|
|
108
139
|
Next time you ask
|
|
109
140
|
→ The model finds the previous memory → coherent responses
|
|
@@ -114,15 +145,7 @@ Next time you ask
|
|
|
114
145
|
|
|
115
146
|
## What gets saved
|
|
116
147
|
|
|
117
|
-
Every turn (question + answer)
|
|
118
|
-
|
|
119
|
-
- **Text** categorized by wing (developer, creative, emotions, family, consciousness)
|
|
120
|
-
- **Knowledge Graph**: automatically extracted facts
|
|
121
|
-
- `decision` → "decided to use TypeScript"
|
|
122
|
-
- `milestone` → "backend deploy completed"
|
|
123
|
-
- `problem` → "chromadb ModuleNotFoundError"
|
|
124
|
-
- `preference` → "prefer Svelte over React"
|
|
125
|
-
- `emotional` → "frustrated with Docker compose"
|
|
148
|
+
Every turn (question + answer) is saved as a drawer in MemPalace. No forced categorization — MemPalace's own mining handles organization. The model can optionally record KG facts (decisions, milestones, preferences) during conversation via MCP tools.
|
|
126
149
|
|
|
127
150
|
---
|
|
128
151
|
|
|
@@ -137,15 +160,13 @@ Every turn (question + answer):
|
|
|
137
160
|
│ Query OpenCode DB │
|
|
138
161
|
│ (messages since lastSync)│
|
|
139
162
|
│ ↓ │
|
|
140
|
-
│
|
|
141
|
-
│
|
|
142
|
-
│ Export delta → tmp │
|
|
163
|
+
│ Export sessions → flat │
|
|
164
|
+
│ (no wing subdirs) │
|
|
143
165
|
│ ↓ │
|
|
144
166
|
│ Save state immediately │
|
|
145
167
|
│ ↓ │
|
|
146
|
-
│ mempalace mine (async) │
|
|
147
|
-
│
|
|
148
|
-
│ Extract KG facts │
|
|
168
|
+
│ mempalace mine (async) │
|
|
169
|
+
│ single call, serialized │
|
|
149
170
|
│ ↓ │
|
|
150
171
|
Session idle ─►│ session.idle hook │
|
|
151
172
|
│ (saves last turn) │
|
|
@@ -155,7 +176,7 @@ Every turn (question + answer):
|
|
|
155
176
|
┌──────────────────────────┐
|
|
156
177
|
│ MemPalace │
|
|
157
178
|
│ ~/opencode-memory/ │
|
|
158
|
-
│ Vector DB
|
|
179
|
+
│ Vector DB │
|
|
159
180
|
└──────────────────────────┘
|
|
160
181
|
▲
|
|
161
182
|
│
|
|
@@ -163,7 +184,9 @@ Every turn (question + answer):
|
|
|
163
184
|
│ AGENTS.md + MCP │
|
|
164
185
|
│ The model searches │
|
|
165
186
|
│ MemPalace on every │
|
|
166
|
-
│ question
|
|
187
|
+
│ question. Optionally │
|
|
188
|
+
│ records KG facts via │
|
|
189
|
+
│ kg_add / kg_invalidate │
|
|
167
190
|
└──────────────────────────┘
|
|
168
191
|
```
|
|
169
192
|
|
|
@@ -209,6 +232,36 @@ When set, the plugin writes a debug log to `/tmp/opencode-mempalace.log`.
|
|
|
209
232
|
|
|
210
233
|
---
|
|
211
234
|
|
|
235
|
+
## Recommendations
|
|
236
|
+
|
|
237
|
+
### Model choice affects memory retrieval reliability
|
|
238
|
+
|
|
239
|
+
Empirical data from the MemPalace community (Cat-9a diagnostic) shows that the model's tool-use discipline significantly impacts how reliably `mempalace_search` is invoked:
|
|
240
|
+
|
|
241
|
+
| Model | Skips search | Mean recall |
|
|
242
|
+
|-------|:-:|:-:|
|
|
243
|
+
| gemma4:e4b (4B) | **60%** | 0.417 |
|
|
244
|
+
| qwen3.5:4b (4B, Tau2-tuned) | **13%** | 0.717 |
|
|
245
|
+
|
|
246
|
+
For reliable read-side memory retrieval, **Qwen 3.5 4B+ or equivalent** is recommended as the minimum orchestrator. Smaller or older models may skip memory search on most questions regardless of AGENTS.md instructions.
|
|
247
|
+
|
|
248
|
+
### Forced invocation (belt and suspenders)
|
|
249
|
+
|
|
250
|
+
A plugin-level config flag that injects a mandatory `mempalace_search` directive into the system prompt (on top of AGENTS.md) can recover ~15pp of recall on low-discipline models. This is being evaluated as a future config option — suggestions welcome.
|
|
251
|
+
|
|
252
|
+
### Complementary: upstream OpenCode source adapter
|
|
253
|
+
|
|
254
|
+
The MemPalace project has an upstream PR ([#1484](https://github.com/MemPalace/mempalace/pull/1484)) adding `mempalace mine --source opencode` — a pull-based adapter for retrospective ingest of existing OpenCode sessions. This plugin (push, real-time) and the adapter (pull, backfill) are complementary:
|
|
255
|
+
|
|
256
|
+
| Approach | Direction | Captures |
|
|
257
|
+
|----------|-----------|----------|
|
|
258
|
+
| This plugin | Push | Live conversation turns |
|
|
259
|
+
| `mempalace mine --source opencode` (PR #1484) | Pull | Existing OpenCode session files |
|
|
260
|
+
|
|
261
|
+
For full coverage: install this plugin for live capture, run `mempalace mine --source opencode` once for backfill, never think about it again.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
212
265
|
## License
|
|
213
266
|
|
|
214
267
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync, exec } from "child_process";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync,
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmdirSync, unlinkSync, appendFileSync } from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { createHash } from "crypto";
|
|
@@ -8,7 +8,6 @@ const VENV_PYTHON = join(HOME, ".local/share/pipx/venvs/mempalace/bin/python3");
|
|
|
8
8
|
const MEMPALACE_BIN = join(HOME, ".local/bin/mempalace");
|
|
9
9
|
const OPENCODE_DB = join(HOME, ".local/share/opencode/opencode.db");
|
|
10
10
|
const STATE_FILE = join(HOME, ".mempalace/sync_state.json");
|
|
11
|
-
const KG_DB = join(HOME, ".mempalace/knowledge_graph.sqlite3");
|
|
12
11
|
const OUT_DIR = "/tmp/oc-sessions";
|
|
13
12
|
const TMP_SCRIPT = "/tmp/oc-plugin-query.py";
|
|
14
13
|
const DEBUG = !!process.env.OPENCODE_MEMPALACE_DEBUG;
|
|
@@ -22,7 +21,7 @@ function log(msg) {
|
|
|
22
21
|
}
|
|
23
22
|
catch { }
|
|
24
23
|
}
|
|
25
|
-
let
|
|
24
|
+
let miningLock = false;
|
|
26
25
|
let lastSyncTs = 0;
|
|
27
26
|
function runPython(code) {
|
|
28
27
|
writeFileSync(TMP_SCRIPT, code);
|
|
@@ -33,29 +32,6 @@ function runPython(code) {
|
|
|
33
32
|
unlinkSync(TMP_SCRIPT);
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
|
-
const WING_KEYWORDS = {
|
|
37
|
-
creative: ["eurovision", "music", "art", "film", "game", "festival", "song", "concert"],
|
|
38
|
-
family: ["family", "kid", "son", "daughter", "wife", "husband", "parent", "mother", "father"],
|
|
39
|
-
emotions: ["feel", "happy", "sad", "love", "hate", "angry", "fear", "anxiety", "grateful"],
|
|
40
|
-
consciousness: ["think", "mind", "conscious", "self", "identity", "exist", "meaning"],
|
|
41
|
-
};
|
|
42
|
-
const KG_PATTERNS = {
|
|
43
|
-
decision: ["ho deciso", "ho scelto", "implementato", "added ", "ho aggiunto", "decided", "chosen", "created", "creato", "modified"],
|
|
44
|
-
milestone: ["completato", "finito", "rilasciato", "deployato", "ho completato", "successo", "funziona", "completed", "done", "deployed"],
|
|
45
|
-
problem: ["problema", "bug", "errore", "issue", "fix", "fixato", "risolto", "solved", "fixed", "error"],
|
|
46
|
-
preference: ["preferisco", "mi piace", "non mi piace", "prefer", "like to", "better"],
|
|
47
|
-
emotional: ["sentimento", "emozione", "frustrato", "contento", "happy", "sad", "feel", "emotion"],
|
|
48
|
-
};
|
|
49
|
-
function categorize(text) {
|
|
50
|
-
if (!text)
|
|
51
|
-
return "developer";
|
|
52
|
-
const lower = text.toLowerCase();
|
|
53
|
-
for (const [wing, keywords] of Object.entries(WING_KEYWORDS)) {
|
|
54
|
-
if (keywords.some((kw) => lower.includes(kw)))
|
|
55
|
-
return wing;
|
|
56
|
-
}
|
|
57
|
-
return "developer";
|
|
58
|
-
}
|
|
59
35
|
function hasText(parts) {
|
|
60
36
|
return parts
|
|
61
37
|
.filter((p) => p?.type === "text" && p?.text?.trim())
|
|
@@ -73,9 +49,8 @@ function getLastSync() {
|
|
|
73
49
|
}
|
|
74
50
|
}
|
|
75
51
|
function dbSync() {
|
|
76
|
-
if (
|
|
52
|
+
if (miningLock)
|
|
77
53
|
return;
|
|
78
|
-
pendingSync = true;
|
|
79
54
|
try {
|
|
80
55
|
doDbSync();
|
|
81
56
|
}
|
|
@@ -106,17 +81,15 @@ print(json.dumps(rows))
|
|
|
106
81
|
sessionsArr = JSON.parse(sessions);
|
|
107
82
|
}
|
|
108
83
|
catch {
|
|
109
|
-
pendingSync = false;
|
|
110
84
|
return;
|
|
111
85
|
}
|
|
112
|
-
if (!sessionsArr || sessionsArr.length === 0)
|
|
113
|
-
pendingSync = false;
|
|
86
|
+
if (!sessionsArr || sessionsArr.length === 0)
|
|
114
87
|
return;
|
|
115
|
-
}
|
|
116
88
|
const now = Date.now();
|
|
117
89
|
const exported = [];
|
|
90
|
+
mkdirSync(OUT_DIR, { recursive: true });
|
|
118
91
|
for (const sess of sessionsArr) {
|
|
119
|
-
const [sessId, title
|
|
92
|
+
const [sessId, title] = sess;
|
|
120
93
|
const label = (title || "").replace(/[^a-zA-Z0-9 _-]/g, "_") || (sessId || "").slice(0, 12);
|
|
121
94
|
const prefix = `${new Date().toISOString().slice(0, 10)}_${label.slice(0, 30)}_${(sessId || "").slice(0, 8)}`;
|
|
122
95
|
const msgs = runPython(`
|
|
@@ -158,7 +131,7 @@ print(json.dumps(texts))
|
|
|
158
131
|
];
|
|
159
132
|
for (const m of msgList) {
|
|
160
133
|
const ts = m.ts ? new Date(m.ts).toISOString().slice(11, 19) : "";
|
|
161
|
-
lines.push(`## ${m.role.toUpperCase()}
|
|
134
|
+
lines.push(`## ${m.role.toUpperCase()} \u2014 ${ts}`);
|
|
162
135
|
lines.push("");
|
|
163
136
|
lines.push(m.text);
|
|
164
137
|
lines.push("");
|
|
@@ -166,88 +139,38 @@ print(json.dumps(texts))
|
|
|
166
139
|
const content = lines.join("\n").trim();
|
|
167
140
|
if (!content)
|
|
168
141
|
continue;
|
|
169
|
-
const wing = categorize(content);
|
|
170
142
|
const contentHash = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
171
143
|
const fname = `sync_${prefix}_${contentHash}.txt`;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
writeFileSync(join(wingDir, fname), content + "\n");
|
|
175
|
-
exported.push({ wing, fname, dir: wingDir });
|
|
144
|
+
writeFileSync(join(OUT_DIR, fname), content + "\n");
|
|
145
|
+
exported.push(join(OUT_DIR, fname));
|
|
176
146
|
}
|
|
177
|
-
if (exported.length === 0)
|
|
178
|
-
pendingSync = false;
|
|
147
|
+
if (exported.length === 0)
|
|
179
148
|
return;
|
|
180
|
-
}
|
|
181
|
-
// Save state immediately so next sync doesn't re-process
|
|
182
149
|
writeFileSync(STATE_FILE, JSON.stringify({ last_sync_ms: now }));
|
|
183
150
|
lastSyncTs = Date.now();
|
|
184
|
-
|
|
185
|
-
log(`
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
rmSync(filePath);
|
|
198
|
-
try {
|
|
199
|
-
rmdirSync(wingDir);
|
|
200
|
-
}
|
|
201
|
-
catch { }
|
|
202
|
-
log(`mined ${wing}`);
|
|
203
|
-
// KG extraction after mining
|
|
151
|
+
miningLock = true;
|
|
152
|
+
log(`mining ${exported.length} sessions`);
|
|
153
|
+
exec(`${MEMPALACE_BIN} mine ${OUT_DIR} --mode convos`, {
|
|
154
|
+
encoding: "utf-8",
|
|
155
|
+
timeout: 120000,
|
|
156
|
+
}, (err) => {
|
|
157
|
+
miningLock = false;
|
|
158
|
+
if (err) {
|
|
159
|
+
log(`mine err: ${err.message}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const f of exported) {
|
|
204
163
|
try {
|
|
205
|
-
|
|
206
|
-
from mempalace.config import MempalaceConfig
|
|
207
|
-
import chromadb, json, re
|
|
208
|
-
c = MempalaceConfig()
|
|
209
|
-
col = chromadb.PersistentClient(path=c.palace_path).get_collection(c.collection_name)
|
|
210
|
-
d = col.get(limit=5000, include=["metadatas","documents"])
|
|
211
|
-
p = ${JSON.stringify(KG_PATTERNS)}
|
|
212
|
-
r = []
|
|
213
|
-
for i,m,doc in zip(d.get("ids",[]), d.get("metadatas",[]), d.get("documents",[])):
|
|
214
|
-
if not m or not doc: continue
|
|
215
|
-
rm = m.get("room","")
|
|
216
|
-
if rm not in ("decision","milestone","problem","preference","emotional"): continue
|
|
217
|
-
da = doc.split("Date: ")[1][:10] if "Date: " in doc else ""
|
|
218
|
-
if not da: continue
|
|
219
|
-
if not any(pat in doc.lower() for pat in p.get(rm,[])): continue
|
|
220
|
-
for ln in doc.strip().split("\\n")[:5]:
|
|
221
|
-
l = ln.strip()
|
|
222
|
-
if l and not l.startswith("#") and len(l) > 20 and len(re.findall(r"[^a-zA-Z0-9\\s]", l))/len(l) <= 0.3:
|
|
223
|
-
r.append([da, rm, l[:120]]); break
|
|
224
|
-
print(json.dumps(r))
|
|
225
|
-
`);
|
|
226
|
-
const nf = JSON.parse(raw) || [];
|
|
227
|
-
if (nf.length > 0) {
|
|
228
|
-
const ex = runPython(`import sqlite3,json;d=sqlite3.connect(${JSON.stringify(KG_DB)});r=d.execute("SELECT predicate,object FROM triples WHERE subject='user'").fetchall();d.close();print(json.dumps(r))`);
|
|
229
|
-
const seen = new Set();
|
|
230
|
-
try {
|
|
231
|
-
for (const [p, o] of JSON.parse(ex))
|
|
232
|
-
seen.add(p + "::" + o);
|
|
233
|
-
}
|
|
234
|
-
catch { }
|
|
235
|
-
const ins = [];
|
|
236
|
-
for (const [d, t, x] of nf) {
|
|
237
|
-
const k = t + "::" + x;
|
|
238
|
-
if (seen.has(k))
|
|
239
|
-
continue;
|
|
240
|
-
seen.add(k);
|
|
241
|
-
ins.push(`INSERT OR IGNORE INTO triples(id,subject,predicate,object,valid_from,confidence,extracted_at) VALUES('t_user_${t}_${createHash("sha256").update(x).digest("hex").slice(0, 12)}','user','${t}','${x.replace(/'/g, "''")}','${d}',1.0,datetime('now'))`);
|
|
242
|
-
}
|
|
243
|
-
if (ins.length > 0) {
|
|
244
|
-
runPython(`import sqlite3;db=sqlite3.connect(${JSON.stringify(KG_DB)});[db.execute(s) for s in ${JSON.stringify(ins)}];db.commit();db.close()`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
164
|
+
unlinkSync(f);
|
|
247
165
|
}
|
|
248
166
|
catch { }
|
|
249
|
-
}
|
|
250
|
-
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
rmdirSync(OUT_DIR);
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
log("mine done");
|
|
173
|
+
});
|
|
251
174
|
}
|
|
252
175
|
export default (async () => {
|
|
253
176
|
mkdirSync(OUT_DIR, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mempalace-persistence",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "OpenCode plugin — auto-sync conversations to MemPalace memory in real-time
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|
|
7
|
-
"files": [
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
8
10
|
"scripts": {
|
|
9
11
|
"build": "tsc",
|
|
10
12
|
"prepublishOnly": "npm run build"
|
|
11
13
|
},
|
|
12
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opencode",
|
|
16
|
+
"mempalace",
|
|
17
|
+
"memory",
|
|
18
|
+
"opencode-plugin",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
13
21
|
"author": "geco",
|
|
14
22
|
"license": "MIT",
|
|
15
23
|
"type": "module",
|