opencode-mempalace-persistence 1.1.1 → 1.2.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 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,26 @@ 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
+ No AGENTS.md changes needed — the context is guaranteed regardless of model discipline.
125
+
126
+ ### 6. MemPalace (if not already installed)
108
127
 
109
128
  ```bash
110
129
  # 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.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",