@ephemushroom/opencode-claude-mem 0.2.4 → 0.2.5

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.
Files changed (3) hide show
  1. package/README.md +165 -219
  2. package/dist/index.js +110 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,57 +1,20 @@
1
1
  # opencode-claude-mem
2
2
 
3
- OpenCode plugin for [Claude-Mem](https://github.com/thedotmack/claude-mem) persistent memory system.
3
+ Persistent memory for [OpenCode](https://opencode.ai), powered by
4
+ [Claude-Mem](https://github.com/thedotmack/claude-mem).
4
5
 
5
- Enables OpenCode to share the same memory database as Claude Code — observations, summaries, and context injection all work across both editors.
6
+ Share the same Claude-Mem worker, database, and search tools between Claude Code
7
+ and OpenCode. Once connected, previous observations and summaries are injected
8
+ into new OpenCode sessions automatically.
6
9
 
7
- ## How It Works
8
-
9
- This plugin communicates with the Claude-Mem worker service (HTTP API on port 37777) to:
10
-
11
- - **Inject memory context** — previous session context is automatically injected into the system prompt on every LLM call
12
- - **Capture tool observations** — every tool execution is recorded as a structured observation
13
- - **Summarize sessions** — when a session goes idle, the last user/assistant messages are sent for summarization
14
- - **Search memory** — memory search is available via claude-mem's MCP server
15
-
16
- ## Differences from Claude Code Version
17
-
18
- The Claude Code version of claude-mem uses shell-based hooks where `SessionStart` stdout is automatically injected into the system prompt — one-time, at session start.
19
-
20
- This OpenCode version works differently:
21
-
22
- | | Claude Code | OpenCode (this plugin) |
23
- |---|---|---|
24
- | **Architecture** | Shell commands (`node script.js`) | JavaScript plugin API |
25
- | **Context injection** | `SessionStart` hook stdout → system prompt (once) | `experimental.chat.system.transform` → system prompt (every LLM call, session-level cache) |
26
- | **Context freshness** | Snapshot at session start | Cached once per session (same as Claude Code) |
27
- | **Session init** | `UserPromptSubmit` hook | `chat.message` hook with real user prompt |
28
- | **Observations** | `PostToolUse` hook | `tool.execute.after` hook (with circular memory protection) |
29
- | **Summarization** | `Stop` hook | `session.idle` event |
30
- | **Memory search** | MCP server | MCP server (same — no custom tool needed) |
10
+ > **Note:** This plugin is a thin OpenCode adapter for an existing Claude-Mem
11
+ > installation. It does **not** install Claude-Mem, manage slash commands, or
12
+ > start the worker for you.
31
13
 
32
- Both versions behave the same way: context is fetched once per session and cached. The OpenCode plugin uses `experimental.chat.system.transform` which fires on every LLM call, but the context is session-level cached so the worker is only called once.
14
+ ## Quick Start
33
15
 
34
- ## Prerequisites
35
-
36
- - [Claude Code](https://claude.com/claude-code) with [claude-mem plugin](https://github.com/thedotmack/claude-mem) installed and running
37
- - [OpenCode](https://opencode.ai) with plugin support
38
-
39
- ## Installation
40
-
41
- ### Step 1: Install Claude-Mem (if not already)
42
-
43
- In Claude Code terminal:
44
-
45
- ```
46
- /plugin marketplace add thedotmack/claude-mem
47
- /plugin install claude-mem
48
- ```
49
-
50
- Restart Claude Code. The worker service will start automatically on port 37777.
51
-
52
- ### Step 2: Add the Plugin
53
-
54
- Add to your `opencode.json` (project or global `~/.config/opencode/opencode.json`):
16
+ 1. Install and configure Claude-Mem in Claude Code.
17
+ 2. Add this plugin to your `opencode.json`:
55
18
 
56
19
  ```json
57
20
  {
@@ -59,241 +22,224 @@ Add to your `opencode.json` (project or global `~/.config/opencode/opencode.json
59
22
  }
60
23
  ```
61
24
 
62
- Restart OpenCode. The plugin will be installed automatically.
25
+ 3. Restart OpenCode.
26
+ 4. Start a session — memory context will be injected automatically when the
27
+ Claude-Mem worker is available.
63
28
 
64
- ### Step 3: Verify
65
-
66
- ```bash
67
- # Check worker is running
68
- curl -s http://127.0.0.1:37777/api/health
29
+ ## Key Features
69
30
 
70
- # Restart OpenCode — you should see toast: "Memory active · {project}"
71
- ```
72
-
73
- ## Usage
31
+ - **Shared Memory** — Uses the same Claude-Mem worker and memory store as
32
+ Claude Code.
33
+ - **Automatic Context Injection** — Injects relevant project memory into the
34
+ system prompt for new OpenCode turns.
35
+ - **Compaction Support** — Re-injects memory context during OpenCode session
36
+ compaction so long conversations keep their historical context.
37
+ - **Observation Capture** — Sends tool observations to Claude-Mem for future
38
+ retrieval and summarization.
39
+ - **Observation Hardening** — Skips low-value meta tools, strips
40
+ `<claude-mem-context>` and `<private>` tags before storage, and truncates
41
+ oversized observation payloads by UTF-8 byte size.
42
+ - **Graceful Degradation** — If the worker is offline, the plugin fails open
43
+ and OpenCode continues to work normally.
74
44
 
75
- ### Automatic (no action needed)
76
-
77
- Once installed, the plugin works automatically:
78
-
79
- - **System prompt injection** — memory context is automatically injected into every LLM call via `experimental.chat.system.transform`. The LLM sees your project's observation history, past decisions, and session summaries. Context is cached once per session to avoid redundant worker calls.
80
- - **Tool observation capture** — every tool execution (file reads, edits, searches, etc.) is recorded as an observation in the memory database. Claude-mem's own MCP tools are automatically filtered to prevent circular memory.
81
- - **Session summarization** — when a session goes idle, the last user/assistant exchange is summarized and stored.
82
- - **Toast notification** — on session start, a "Memory active · {project}" toast confirms the plugin is connected to the worker.
83
-
84
- **Note**: Memory context is injected transparently into every conversation. You don't need to run any commands — the plugin works automatically in the background.
85
-
86
- ### Memory Search
87
-
88
- Memory search is provided by claude-mem's MCP server. If you have the claude-mem MCP configured in OpenCode, the LLM can search project history directly via MCP tools — no plugin-level tool needed.
89
-
90
- ### Alternative: Manual Installation (from source)
91
-
92
- If you prefer to install from source instead of npm:
45
+ ## How It Works
93
46
 
94
- ```bash
95
- git clone https://github.com/Ephemushroom/opencode-claude-mem.git
96
- cd opencode-claude-mem
97
- bun install
98
- bun run build
99
47
  ```
100
-
101
- Then symlink into OpenCode:
102
-
103
- **macOS / Linux:**
104
-
105
- ```bash
106
- mkdir -p ~/.config/opencode/plugin
107
- ln -sf "$(pwd)/dist/index.js" ~/.config/opencode/plugin/claude-mem.js
48
+ OpenCode session
49
+ |
50
+ |-- plugin loads
51
+ | '-- connect to Claude-Mem worker on port 37777
52
+ |
53
+ |-- session.created ................ track session + reset cache
54
+ |-- chat.message ................... init session with real user prompt
55
+ |-- tool.execute.after ............. send tool observation
56
+ |-- system.transform ............... inject memory into system prompt
57
+ |-- session.compacting ............. preserve memory during compaction
58
+ '-- session.idle ................... summarize + complete session
108
59
  ```
109
60
 
110
- **Windows (requires elevated terminal):**
61
+ The plugin is intentionally small. It only adapts OpenCode hook events to the
62
+ Claude-Mem worker HTTP API. All indexing, summarization, memory search, and
63
+ storage stay in upstream Claude-Mem.
111
64
 
112
- ```powershell
113
- # Option A: If you have gsudo / sudo installed
114
- sudo powershell -Command "New-Item -ItemType SymbolicLink -Path '$env:USERPROFILE\.config\opencode\plugin\claude-mem.js' -Target 'D:\path\to\opencode-claude-mem\dist\index.js'"
65
+ ## Architecture
115
66
 
116
- # Option B: Run PowerShell as Administrator
117
- New-Item -ItemType SymbolicLink -Path "$env:USERPROFILE\.config\opencode\plugin\claude-mem.js" -Target "D:\path\to\opencode-claude-mem\dist\index.js"
67
+ ```
68
+ OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker (port 37777) <-> SQLite + ChromaDB
118
69
  ```
119
70
 
120
- ---
121
-
122
- ## Windows-Specific Setup
71
+ ### Hook Mapping
123
72
 
124
- ### Python Version for ChromaDB
73
+ | Claude Code | OpenCode plugin | Purpose |
74
+ |---|---|---|
75
+ | `SessionStart` | `experimental.chat.system.transform` | Inject memory context |
76
+ | `SessionStart` | `experimental.session.compacting` | Preserve memory during compaction |
77
+ | `UserPromptSubmit` | `chat.message` | Initialize session with real user prompt |
78
+ | `PostToolUse` | `tool.execute.after` | Capture tool observations |
79
+ | `Stop` | `event` (`session.idle`) | Summarize the session |
80
+ | `SessionEnd` | `event` (`session.idle`) | Mark session complete |
125
81
 
126
- Claude-Mem uses ChromaDB for vector semantic search. ChromaDB's `chroma-mcp` tool requires **Python ≤ 3.13** (Pydantic V1 is incompatible with Python 3.14+).
82
+ ### Worker API Endpoints Used
127
83
 
128
- If `uv` defaults to Python 3.14, you'll see:
84
+ | Method | Endpoint | Purpose |
85
+ |---|---|---|
86
+ | `GET` | `/api/health` | Health check |
87
+ | `GET` | `/api/context/inject?project={name}` | Fetch formatted memory context |
88
+ | `POST` | `/api/sessions/init` | Initialize session |
89
+ | `POST` | `/api/sessions/observations` | Store tool observation |
90
+ | `POST` | `/api/sessions/summarize` | Trigger summarization |
91
+ | `POST` | `/api/sessions/complete` | Complete session |
129
92
 
130
- ```
131
- Chroma connection failed: Chroma server not reachable.
132
- ```
93
+ ## Installation
133
94
 
134
- **Fix — set global Python version:**
95
+ ### Prerequisites
135
96
 
136
- ```bash
137
- uv python pin --global 3.13
138
- ```
97
+ - [Claude Code](https://claude.com/claude-code) with
98
+ [Claude-Mem](https://github.com/thedotmack/claude-mem) installed
99
+ - [OpenCode](https://opencode.ai) with plugin support
100
+ - A running Claude-Mem worker on port `37777` (default)
139
101
 
140
- This creates a global `.python-version` file in `%APPDATA%\uv\` so all `uvx` calls default to 3.13.
102
+ ### Step 1: Install Claude-Mem
141
103
 
142
- ### ChromaDB on Windows x64
104
+ In Claude Code:
143
105
 
144
- The npm `chromadb` package only supports Windows ARM64, not x64. The worker will fail to start Chroma natively.
106
+ ```text
107
+ /plugin marketplace add thedotmack/claude-mem
108
+ /plugin install claude-mem
109
+ ```
145
110
 
146
- **Fix — use Python ChromaDB instead:**
111
+ Restart Claude Code so the worker can start and initialize its data directory.
147
112
 
148
- ```bash
149
- # Install Python ChromaDB CLI
150
- uv tool install chromadb --python 3.13
151
- ```
113
+ ### Step 2: Add the OpenCode Plugin
152
114
 
153
- Update `~/.claude-mem/settings.json`:
115
+ Add this plugin to your project or global `opencode.json`:
154
116
 
155
117
  ```json
156
118
  {
157
- "CLAUDE_MEM_CHROMA_MODE": "remote"
119
+ "plugin": ["@ephemushroom/opencode-claude-mem"]
158
120
  }
159
121
  ```
160
122
 
161
- Start Chroma server manually:
123
+ Then restart OpenCode.
124
+
125
+ ### Step 3: Verify
162
126
 
163
127
  ```bash
164
- chroma run --path %USERPROFILE%\.claude-mem\vector-db --host 127.0.0.1 --port 8000
128
+ curl -s http://127.0.0.1:37777/api/health
165
129
  ```
166
130
 
167
- > **Note**: Chroma server must be running before Claude Code starts. Consider creating a startup script or scheduled task.
131
+ If the worker is healthy, OpenCode should show a toast like
132
+ `Memory active · <project>` when a session starts.
168
133
 
169
- ### ChromaDB Embedding Model Crash (Bun Runtime)
134
+ ## Usage
170
135
 
171
- After connecting to Chroma, the worker may crash with:
136
+ Once installed, the plugin works automatically:
172
137
 
173
- ```
174
- Collection setup failed: undefined is not an object
175
- (evaluating 'e.cacheDir = ...')
176
- ```
138
+ - **Context injection** — Memory is injected into the system prompt on each LLM
139
+ call, with session-level caching to avoid repeated worker requests.
140
+ - **Compaction preservation** — The same cached memory context is pushed into
141
+ OpenCode’s compaction path so memory survives conversation compression.
142
+ - **Tool observation capture** — Tool executions are stored as observations,
143
+ except for low-value/meta tools and Claude-Mem search tools.
144
+ - **Session summarization** — On `session.idle`, the plugin fetches the latest
145
+ user and assistant messages and asks Claude-Mem to summarize them.
177
146
 
178
- This happens because `@huggingface/transformers` returns `undefined` when imported through Bun's CJS shim.
147
+ ## Memory Search
179
148
 
180
- **Fix — patch `worker-service.cjs`:**
149
+ Memory search is provided by Claude-Mem’s MCP server, not by this plugin.
181
150
 
182
- Locate the bundled worker file:
151
+ If your OpenCode environment already has Claude-Mem MCP tools configured, the
152
+ assistant can query project memory directly with Claude-Mem’s search workflow:
183
153
 
184
- ```
185
- ~/.claude/plugins/cache/thedotmack/claude-mem/<version>/scripts/worker-service.cjs
186
- ```
154
+ 1. `search(query="...")`
155
+ 2. `timeline(anchor=ID)`
156
+ 3. `get_observations(ids=[...])`
187
157
 
188
- Find this pattern (minified):
158
+ This plugin focuses on sending memory data to the worker and injecting returned
159
+ context back into OpenCode sessions.
189
160
 
190
- ```javascript
191
- let{env:e}=await Promise.resolve().then(()=>(F9(),N9));
192
- ```
161
+ ## Key Implementation Details
193
162
 
194
- Replace with:
163
+ - **Thin client architecture** — `src/index.ts` handles OpenCode hooks and
164
+ session state; `src/worker-client.ts` is a static HTTP client.
165
+ - **No console logging** — The plugin never writes to `console.*` because that
166
+ can corrupt the OpenCode TUI.
167
+ - **Deferred toast** — Health toasts only happen after hook execution begins,
168
+ avoiding startup crashes caused by early TUI access.
169
+ - **Real prompt initialization** — `chat.message` sends the actual user prompt
170
+ to Claude-Mem instead of a synthetic placeholder.
171
+ - **Real summarize payloads** — On `session.idle`, the plugin reads the latest
172
+ session messages and sends the last user and assistant messages for summary.
173
+ - **Context caching** — Memory context is fetched once per session and reused
174
+ across prompt injection and compaction.
175
+ - **Circular memory protection** — Injected context is wrapped in
176
+ `<claude-mem-context>` tags, Claude-Mem MCP search tools are skipped, and
177
+ memory-related tags are stripped before storing observations.
178
+ - **UTF-8 truncation** — Large observation outputs are capped by byte size to
179
+ reduce token waste and avoid oversize worker payloads.
180
+ - **Field name correctness** — Worker payloads use `contentSessionId`, not
181
+ `claudeSessionId`.
182
+
183
+ ## Differences from `bloodf/opencode-mem`
184
+
185
+ This project intentionally stays smaller in scope.
186
+
187
+ - **This plugin does**: bridge OpenCode hooks to an already-installed
188
+ Claude-Mem worker.
189
+ - **This plugin does not**: auto-install Claude-Mem, auto-edit OpenCode config,
190
+ auto-copy skills, auto-register slash commands, or auto-start the worker.
191
+
192
+ That keeps the runtime behavior predictable and leaves worker ownership with the
193
+ upstream Claude-Mem installation.
195
194
 
196
- ```javascript
197
- let{env:e}=await import("@huggingface/transformers");
198
- ```
195
+ ## Troubleshooting
196
+
197
+ ### No memory appears in OpenCode
199
198
 
200
- Then install the dependency:
199
+ - Confirm the worker is running:
201
200
 
202
201
  ```bash
203
- cd ~/.claude/plugins/cache/thedotmack/claude-mem/<version>
204
- bun install
202
+ curl -s http://127.0.0.1:37777/api/health
205
203
  ```
206
204
 
207
- > ⚠️ **This patch is lost on plugin updates.** You'll need to re-apply after each claude-mem version upgrade.
205
+ - Make sure Claude-Mem has already been installed and used from Claude Code.
206
+ - Start a fresh OpenCode session after the worker is healthy.
208
207
 
209
- ### Verifying ChromaDB Works
208
+ ### OpenCode shows `Worker offline`
210
209
 
211
- ```bash
212
- # Check Chroma server
213
- curl -s http://127.0.0.1:8000/api/v1/heartbeat
210
+ - Restart Claude Code to bring Claude-Mem back up.
211
+ - Verify the worker port is still `37777`.
214
212
 
215
- # Check worker health
216
- curl -s http://127.0.0.1:37777/api/health
213
+ ### OpenCode crashes on startup
217
214
 
218
- # Check for sync errors in worker logs
219
- # Open http://localhost:37777 in browser for the web viewer
220
- ```
215
+ - Update to a version with deferred TUI access.
216
+ - Ensure there are no `console.log`, `console.warn`, or `console.error` calls in
217
+ local plugin modifications.
221
218
 
222
- When working correctly, worker logs should show:
219
+ ### Observations are missing or incomplete
223
220
 
224
- ```
225
- CHROMA_SYNC connected (remote mode, 127.0.0.1:8000)
226
- ```
221
+ - Ensure the worker API payload uses `contentSessionId`.
222
+ - Be aware that low-value meta tools and Claude-Mem search tools are skipped by
223
+ design.
224
+ - Very large tool outputs are truncated before storage.
227
225
 
228
- > **ChromaDB is optional.** Without it, claude-mem falls back to SQLite FTS5 full-text search. You lose semantic/vector search but keyword search still works.
226
+ ### Memory search is unavailable
229
227
 
230
- ---
228
+ - This plugin does not configure MCP tools for you.
229
+ - Configure Claude-Mem’s MCP server separately in your OpenCode environment if
230
+ you want in-editor memory search.
231
231
 
232
232
  ## Development
233
233
 
234
- After editing source:
235
-
236
234
  ```bash
235
+ bun install
237
236
  bun run build
238
- # Restart OpenCode to pick up changes
239
- ```
240
-
241
- If using symlink install, changes are picked up after rebuild. If using npm install, bump version and republish.
242
-
243
- ## Architecture
244
-
237
+ bun run lint
238
+ bun run fmt:check
245
239
  ```
246
- OpenCode ←→ Plugin (this repo) ←→ Claude-Mem Worker (port 37777) ←→ SQLite + ChromaDB
247
- ```
248
-
249
- The plugin is a thin HTTP client. All heavy lifting (LLM processing, storage, search) happens in the worker.
250
-
251
- ### Hook Mapping (Claude Code → OpenCode)
252
-
253
- | Claude Code Hook | OpenCode Hook | Purpose |
254
- |-----------------|---------------|---------|
255
- | `SessionStart` → context | `experimental.chat.system.transform` | Inject memory into system prompt (every LLM call, session-level cache) |
256
- | `UserPromptSubmit` → session-init | `chat.message` | Init session with real user prompt |
257
- | `PostToolUse` → observation | `tool.execute.after` | Capture tool executions |
258
- | `Stop` → summarize | `event` (session.idle) | Summarize with real message content |
259
- | `Stop` → session-complete | `event` (session.idle) | Mark session complete |
260
- | *(no equivalent)* | `config` + `command.execute.before` | `/memory` command for user-visible context |
261
-
262
- ### Worker API Endpoints Used
263
-
264
- | Method | Endpoint | Purpose |
265
- |--------|----------|---------|
266
- | `GET` | `/api/health` | Health check |
267
- | `GET` | `/api/context/inject?project={name}` | Get formatted context for system prompt |
268
- | `POST` | `/api/sessions/init` | Initialize session |
269
- | `POST` | `/api/sessions/observations` | Send tool observation |
270
- | `POST` | `/api/sessions/summarize` | Trigger summarization |
271
- | `POST` | `/api/sessions/complete` | Complete session |
272
-
273
- ## Key Implementation Details
274
-
275
- - **Field name**: All worker API calls use `contentSessionId` (not `claudeSessionId`) — using the wrong field name causes silent failures
276
- - **No console output**: All `console.log/warn/error` removed to avoid corrupting OpenCode TUI
277
- - **Deferred toast**: `client.tui.showToast()` is deferred to first hook invocation (TUI not ready during plugin init, calling it crashes OpenCode)
278
- - **Real prompt**: `chat.message` hook extracts actual user input instead of hardcoded "SESSION_START"
279
- - **Real summarize**: `session.idle` fetches last user/assistant messages via `client.session.messages()` for meaningful summaries
280
- - **Context caching**: `getCachedContext()` caches context once per session (invalidated on `session.created`) to avoid redundant worker calls
281
- - **Content-type handling**: Worker returns `text/plain` markdown — `getContext()` checks content-type and uses `response.text()` instead of `response.json()`
282
- - **Context tag wrapping**: Injected context is wrapped in `<claude-mem-context>` tags so the worker's tag-stripping logic can remove it from observations, preventing circular memory
283
- - **Circular memory protection**: `tool.execute.after` filters out claude-mem's own MCP tools (`claude-mem_mcp-search_*`) to prevent search results from being re-recorded as observations
284
- - **`/memory` command**: Registered via `config` hook, handled by `command.execute.before` — displays context as an `ignored: true` message (visible to user, not sent to LLM)
285
-
286
- ## Troubleshooting
287
240
 
288
- | Symptom | Cause | Fix |
289
- |---------|-------|-----|
290
- | No toast on startup | Worker not running | Start Claude Code first, or check `curl http://127.0.0.1:37777/api/health` |
291
- | "Worker offline" toast | Worker crashed or not started | Restart Claude Code |
292
- | OpenCode crashes on startup | Plugin calling TUI too early | Update to latest version (deferred toast fix) |
293
- | TUI display corrupted | console.log in plugin code | Ensure no console output in source |
294
- | "SESSION_START" in prompts | Old version bug | Update — `session.created` no longer calls sessionInit |
295
- | Chroma sync failed | Python version or Chroma not running | See [Windows-Specific Setup](#windows-specific-setup) |
296
- | Observations not saved | Wrong field name | Ensure `contentSessionId` (not `claudeSessionId`) |
241
+ If you edit source code locally, rebuild and restart OpenCode to pick up the new
242
+ plugin bundle.
297
243
 
298
244
  ## License
299
245
 
package/dist/index.js CHANGED
@@ -98,6 +98,93 @@ class WorkerClient {
98
98
  }
99
99
 
100
100
  // src/index.ts
101
+ var MAX_OBSERVATION_BYTES = 24 * 1024;
102
+ var MAX_TAG_REPLACEMENTS = 100;
103
+ var META_TOOLS = new Set([
104
+ "askuserquestion",
105
+ "getmcpresource",
106
+ "listmcpresourcestool",
107
+ "listmcptools",
108
+ "skill",
109
+ "slashcommand",
110
+ "todowrite"
111
+ ]);
112
+ var PRIVATE_TAG_REGEX = /<private>[\s\S]*?<\/private>/g;
113
+ var CONTEXT_TAG_REGEX = /<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g;
114
+ function stripTaggedContent(text) {
115
+ if (!text) {
116
+ return text;
117
+ }
118
+ let result = text;
119
+ let replacements = 0;
120
+ while (replacements < MAX_TAG_REPLACEMENTS && PRIVATE_TAG_REGEX.test(result)) {
121
+ PRIVATE_TAG_REGEX.lastIndex = 0;
122
+ result = result.replace(PRIVATE_TAG_REGEX, "");
123
+ replacements++;
124
+ }
125
+ PRIVATE_TAG_REGEX.lastIndex = 0;
126
+ while (replacements < MAX_TAG_REPLACEMENTS && CONTEXT_TAG_REGEX.test(result)) {
127
+ CONTEXT_TAG_REGEX.lastIndex = 0;
128
+ result = result.replace(CONTEXT_TAG_REGEX, "");
129
+ replacements++;
130
+ }
131
+ CONTEXT_TAG_REGEX.lastIndex = 0;
132
+ return result.trim();
133
+ }
134
+ function sanitizeObservationValue(value) {
135
+ if (typeof value === "string") {
136
+ return stripTaggedContent(value);
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return value.map((item) => sanitizeObservationValue(item));
140
+ }
141
+ if (value && typeof value === "object") {
142
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, sanitizeObservationValue(item)]));
143
+ }
144
+ return value;
145
+ }
146
+ function truncateUtf8Bytes(text, maxBytes) {
147
+ const encoder = new TextEncoder;
148
+ if (encoder.encode(text).length <= maxBytes) {
149
+ return text;
150
+ }
151
+ const suffix = `
152
+ [truncated]`;
153
+ const suffixBytes = encoder.encode(suffix).length;
154
+ const budget = Math.max(maxBytes - suffixBytes, 0);
155
+ let low = 0;
156
+ let high = text.length;
157
+ while (low < high) {
158
+ const mid = Math.ceil((low + high) / 2);
159
+ const candidate = text.slice(0, mid);
160
+ if (encoder.encode(candidate).length <= budget) {
161
+ low = mid;
162
+ } else {
163
+ high = mid - 1;
164
+ }
165
+ }
166
+ return `${text.slice(0, low)}${suffix}`;
167
+ }
168
+ function shouldSkipObservationTool(toolName) {
169
+ if (!toolName) {
170
+ return true;
171
+ }
172
+ const normalizedName = toolName.toLowerCase();
173
+ return normalizedName.startsWith("claude-mem_mcp-search_") || META_TOOLS.has(normalizedName);
174
+ }
175
+ function normalizeToolOutput(output) {
176
+ if (typeof output === "string") {
177
+ return output;
178
+ }
179
+ if (output === undefined || output === null) {
180
+ return "";
181
+ }
182
+ try {
183
+ return JSON.stringify(output);
184
+ } catch {
185
+ return String(output);
186
+ }
187
+ }
101
188
  var ClaudeMemPlugin = async (ctx) => {
102
189
  const { project, directory, client } = ctx;
103
190
  const projectRoot = directory || process.cwd();
@@ -227,6 +314,25 @@ var ClaudeMemPlugin = async (ctx) => {
227
314
  output.system.push(`<claude-mem-context>
228
315
  [Claude-Mem] Memory Active. Previous Context:
229
316
  ${context}
317
+ </claude-mem-context>`);
318
+ }
319
+ } catch {}
320
+ },
321
+ "experimental.session.compacting": async (input, output) => {
322
+ const sessionId = input.sessionID;
323
+ if (sessionId) {
324
+ await ensureSessionInit(sessionId);
325
+ }
326
+ const isHealthy = await checkWorkerAndToast();
327
+ if (!isHealthy) {
328
+ return;
329
+ }
330
+ try {
331
+ const context = await getCachedContext();
332
+ if (context) {
333
+ output.context.push(`<claude-mem-context>
334
+ [Claude-Mem] Memory Active. Previous Context:
335
+ ${context}
230
336
  </claude-mem-context>`);
231
337
  }
232
338
  } catch {}
@@ -236,12 +342,14 @@ ${context}
236
342
  if (!sessionId) {
237
343
  return;
238
344
  }
239
- if (input.tool.startsWith("claude-mem_mcp-search_")) {
345
+ if (shouldSkipObservationTool(input.tool)) {
240
346
  return;
241
347
  }
242
348
  await ensureSessionInit(sessionId);
243
349
  try {
244
- await WorkerClient.sendObservation(sessionId, input.tool, input.args || {}, output.output, projectRoot);
350
+ const sanitizedToolInput = sanitizeObservationValue(input.args || {});
351
+ const sanitizedToolOutput = truncateUtf8Bytes(stripTaggedContent(normalizeToolOutput(output.output)), MAX_OBSERVATION_BYTES);
352
+ await WorkerClient.sendObservation(sessionId, input.tool, sanitizedToolInput, sanitizedToolOutput, projectRoot);
245
353
  } catch {}
246
354
  }
247
355
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ephemushroom/opencode-claude-mem",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "OpenCode plugin for Claude-Mem persistent memory system",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",