opencode-mempalace-persistence 1.1.1 → 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # opencode-mempalace-persistence
2
2
 
3
- > **Community plugin** — not officially maintained by the MemPalace team. Fully open source, ~120 lines of TypeScript.
3
+ > **Community plugin** — not officially maintained by the MemPalace team. Fully open source, ~200 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
 
@@ -104,7 +104,47 @@ This is loaded automatically at session start via `instructions` in opencode.jso
104
104
  }
105
105
  ```
106
106
 
107
- ### 5. MemPalace (if not already installed)
107
+ ### 5. (Optional) Auto-inject memory context
108
+
109
+ By default, the model must search MemPalace on its own via AGENTS.md instructions. For models with poor tool-use discipline, enable auto-injection — the plugin injects identity + relevant memories directly into the prompt:
110
+
111
+ ```json
112
+ {
113
+ "plugin": ["opencode-mempalace-persistence"],
114
+ "mempalace": {
115
+ "autoInjectContext": true
116
+ }
117
+ }
118
+ ```
119
+
120
+ When enabled, on every user message:
121
+ - **First message**: Injects your identity from `~/.mempalace/identity.txt`
122
+ - **Every message**: Runs `mempalace search` and injects relevant results
123
+
124
+ The context is guaranteed regardless of model discipline. Since the plugin handles search and identity injection, you can simplify AGENTS.md — keep only the Knowledge Graph management steps:
125
+
126
+ ```markdown
127
+ # Memory & Knowledge instructions
128
+
129
+ ### Step 1 — Query Knowledge Graph
130
+ Call `mempalace_mempalace_kg_query` for entity "user" to retrieve relevant facts.
131
+
132
+ ### Step 2 — Record Knowledge Graph facts
133
+ After responding, call `mempalace_mempalace_kg_add` for any new facts found.
134
+ ```
135
+
136
+ And remove `"~/.mempalace/identity.txt"` from `instructions` in your opencode.json — the plugin injects it automatically.
137
+
138
+ **Summary: with vs without autoInjectContext**
139
+
140
+ | Feature | Without (default) | With `autoInjectContext: true` |
141
+ |---------|:-:|:-:|
142
+ | Memory search | Model calls `mempalace_search` (AGENTS.md) | Plugin injects automatically |
143
+ | Identity | `instructions: ["identity.txt"]` | Plugin injects automatically |
144
+ | AGENTS.md needed | Full (search + KG + identity) | Minimal (KG management only) |
145
+ | Depends on model discipline | Yes | No |
146
+
147
+ ### 6. MemPalace (if not already installed)
108
148
 
109
149
  ```bash
110
150
  # Install (requires mempalace>=3.3.5 for HNSW corruption fix)
package/dist/index.d.ts CHANGED
@@ -12,6 +12,12 @@ declare const _default: () => Promise<{
12
12
  message: import("@opencode-ai/sdk").UserMessage;
13
13
  parts: import("@opencode-ai/sdk").Part[];
14
14
  }) => Promise<void>;
15
+ "experimental.chat.messages.transform": (_input: {}, output: {
16
+ messages: {
17
+ info: import("@opencode-ai/sdk").Message;
18
+ parts: import("@opencode-ai/sdk").Part[];
19
+ }[];
20
+ }) => Promise<void>;
15
21
  event: ({ event }: any) => Promise<void>;
16
22
  }>;
17
23
  export default _default;
package/dist/index.js CHANGED
@@ -8,10 +8,14 @@ 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 OPENCODE_CONFIG = join(HOME, ".config/opencode/opencode.json");
12
+ const IDENTITY_FILE = join(HOME, ".mempalace/identity.txt");
11
13
  const OUT_DIR = "/tmp/oc-sessions";
12
14
  const TMP_SCRIPT = "/tmp/oc-plugin-query.py";
13
15
  const DEBUG = !!process.env.OPENCODE_MEMPALACE_DEBUG;
14
16
  const LOG_FILE = "/tmp/opencode-mempalace.log";
17
+ const MAX_INJECT_CHARS = 900;
18
+ const MAX_SEARCH_RESULTS = 3;
15
19
  function log(msg) {
16
20
  if (!DEBUG)
17
21
  return;
@@ -23,6 +27,7 @@ function log(msg) {
23
27
  }
24
28
  let miningLock = false;
25
29
  let lastSyncTs = 0;
30
+ let wakeupDone = false;
26
31
  function runPython(code) {
27
32
  writeFileSync(TMP_SCRIPT, code);
28
33
  try {
@@ -38,6 +43,40 @@ function hasText(parts) {
38
43
  .map((p) => p.text.trim())
39
44
  .join("\n");
40
45
  }
46
+ function isAutoInjectEnabled() {
47
+ try {
48
+ const raw = readFileSync(OPENCODE_CONFIG, "utf-8");
49
+ const cfg = JSON.parse(raw);
50
+ return !!cfg?.mempalace?.autoInjectContext;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ function readIdentity() {
57
+ if (!existsSync(IDENTITY_FILE))
58
+ return "";
59
+ try {
60
+ return readFileSync(IDENTITY_FILE, "utf-8").trim();
61
+ }
62
+ catch {
63
+ return "";
64
+ }
65
+ }
66
+ function mempalaceSearch(query) {
67
+ try {
68
+ const out = execSync(`${MEMPALACE_BIN} search "${query.replace(/"/g, '\\"')}" --results ${MAX_SEARCH_RESULTS}`, {
69
+ encoding: "utf-8",
70
+ timeout: 15000,
71
+ }).trim();
72
+ if (!out || out.includes("No results"))
73
+ return "";
74
+ return out.slice(0, MAX_INJECT_CHARS);
75
+ }
76
+ catch {
77
+ return "";
78
+ }
79
+ }
41
80
  function getLastSync() {
42
81
  if (!existsSync(STATE_FILE))
43
82
  return 0;
@@ -174,7 +213,9 @@ print(json.dumps(texts))
174
213
  }
175
214
  export default (async () => {
176
215
  mkdirSync(OUT_DIR, { recursive: true });
177
- log("loaded");
216
+ const autoInject = isAutoInjectEnabled();
217
+ const identity = readIdentity();
218
+ log(`loaded (autoInjectContext: ${autoInject})`);
178
219
  return {
179
220
  "chat.message": async (_input, output) => {
180
221
  const role = output.message.role;
@@ -186,6 +227,43 @@ export default (async () => {
186
227
  log("user msg - queue sync");
187
228
  setTimeout(() => dbSync(), 500);
188
229
  },
230
+ "experimental.chat.messages.transform": async (_input, output) => {
231
+ if (!autoInject)
232
+ return;
233
+ if (!output?.messages?.length)
234
+ return;
235
+ const lastUser = [...output.messages].reverse().find((m) => m.info?.role === "user");
236
+ if (!lastUser)
237
+ return;
238
+ const query = hasText(lastUser.parts || []);
239
+ if (!query)
240
+ return;
241
+ const injectParts = [];
242
+ if (!wakeupDone) {
243
+ wakeupDone = true;
244
+ if (identity) {
245
+ injectParts.push({
246
+ id: `mp-identity-${Date.now()}`,
247
+ type: "text",
248
+ synthetic: true,
249
+ text: `[MemPalace Identity]\n${identity}\n[/MemPalace Identity]`,
250
+ });
251
+ }
252
+ }
253
+ const memories = mempalaceSearch(query);
254
+ if (memories) {
255
+ injectParts.push({
256
+ id: `mp-recall-${Date.now()}`,
257
+ type: "text",
258
+ synthetic: true,
259
+ text: `[MemPalace Recall]\n${memories}\n[/MemPalace Recall]`,
260
+ });
261
+ }
262
+ if (injectParts.length > 0) {
263
+ lastUser.parts.push(...injectParts);
264
+ log(`injected ${injectParts.length} context blocks`);
265
+ }
266
+ },
189
267
  event: async ({ event }) => {
190
268
  if (event?.type !== "session.idle")
191
269
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mempalace-persistence",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
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",