@ephemushroom/opencode-claude-mem 0.2.3 → 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.
package/README.md CHANGED
@@ -1,59 +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 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
- - **View context on demand** — `/memory` command displays the current memory context in the chat flow
16
-
17
- ## Differences from Claude Code Version
18
-
19
- 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.
20
-
21
- This OpenCode version works differently:
22
-
23
- | | Claude Code | OpenCode (this plugin) |
24
- |---|---|---|
25
- | **Architecture** | Shell commands (`node script.js`) | JavaScript plugin API |
26
- | **Context injection** | `SessionStart` hook stdout → system prompt (once) | `experimental.chat.system.transform` → system prompt (every LLM call, session-level cache) |
27
- | **Context freshness** | Snapshot at session start | Cached once per session (same as Claude Code) |
28
- | **User-visible context** | Displayed in TUI automatically | `/memory` command to view on demand |
29
- | **Session init** | `UserPromptSubmit` hook | `chat.message` hook with real user prompt |
30
- | **Observations** | `PostToolUse` hook | `tool.execute.after` hook (with circular memory protection) |
31
- | **Summarization** | `Stop` hook | `session.idle` event |
32
- | **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.
33
13
 
34
- 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
35
15
 
36
- ## Prerequisites
37
-
38
- - [Claude Code](https://claude.com/claude-code) with [claude-mem plugin](https://github.com/thedotmack/claude-mem) installed and running
39
- - [OpenCode](https://opencode.ai) with plugin support
40
-
41
- ## Installation
42
-
43
- ### Step 1: Install Claude-Mem (if not already)
44
-
45
- In Claude Code terminal:
46
-
47
- ```
48
- /plugin marketplace add thedotmack/claude-mem
49
- /plugin install claude-mem
50
- ```
51
-
52
- Restart Claude Code. The worker service will start automatically on port 37777.
53
-
54
- ### Step 2: Add the Plugin
55
-
56
- 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`:
57
18
 
58
19
  ```json
59
20
  {
@@ -61,243 +22,224 @@ Add to your `opencode.json` (project or global `~/.config/opencode/opencode.json
61
22
  }
62
23
  ```
63
24
 
64
- 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.
65
28
 
66
- ### Step 3: Verify
67
-
68
- ```bash
69
- # Check worker is running
70
- curl -s http://127.0.0.1:37777/api/health
29
+ ## Key Features
71
30
 
72
- # Restart OpenCode — you should see toast: "Memory active · {project}"
73
- ```
74
-
75
- ## 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.
76
44
 
77
- ### Automatic (no action needed)
78
-
79
- Once installed, the plugin works automatically:
80
-
81
- - **System prompt injection** — memory context is 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.
82
- - **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.
83
- - **Session summarization** — when a session goes idle, the last user/assistant exchange is summarized and stored.
84
- - **Toast notification** — on session start, a "Memory active · {project}" toast confirms the plugin is connected to the worker.
85
-
86
- ### `/memory` Command
87
-
88
- Type `/memory` in the chat to display the current memory context inline. This shows you exactly what the LLM sees in its system prompt — the observation index, token economics, and session history.
89
-
90
- ### Memory Search
91
-
92
- 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.
93
-
94
- ### Alternative: Manual Installation (from source)
95
-
96
- If you prefer to install from source instead of npm:
45
+ ## How It Works
97
46
 
98
- ```bash
99
- git clone https://github.com/Ephemushroom/opencode-claude-mem.git
100
- cd opencode-claude-mem
101
- bun install
102
- bun run build
103
47
  ```
104
-
105
- Then symlink into OpenCode:
106
-
107
- **macOS / Linux:**
108
-
109
- ```bash
110
- mkdir -p ~/.config/opencode/plugin
111
- 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
112
59
  ```
113
60
 
114
- **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.
115
64
 
116
- ```powershell
117
- # Option A: If you have gsudo / sudo installed
118
- 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
119
66
 
120
- # Option B: Run PowerShell as Administrator
121
- 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
122
69
  ```
123
70
 
124
- ---
125
-
126
- ## Windows-Specific Setup
71
+ ### Hook Mapping
127
72
 
128
- ### 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 |
129
81
 
130
- 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
131
83
 
132
- 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 |
133
92
 
134
- ```
135
- Chroma connection failed: Chroma server not reachable.
136
- ```
93
+ ## Installation
137
94
 
138
- **Fix — set global Python version:**
95
+ ### Prerequisites
139
96
 
140
- ```bash
141
- uv python pin --global 3.13
142
- ```
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)
143
101
 
144
- This creates a global `.python-version` file in `%APPDATA%\uv\` so all `uvx` calls default to 3.13.
102
+ ### Step 1: Install Claude-Mem
145
103
 
146
- ### ChromaDB on Windows x64
104
+ In Claude Code:
147
105
 
148
- 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
+ ```
149
110
 
150
- **Fix — use Python ChromaDB instead:**
111
+ Restart Claude Code so the worker can start and initialize its data directory.
151
112
 
152
- ```bash
153
- # Install Python ChromaDB CLI
154
- uv tool install chromadb --python 3.13
155
- ```
113
+ ### Step 2: Add the OpenCode Plugin
156
114
 
157
- Update `~/.claude-mem/settings.json`:
115
+ Add this plugin to your project or global `opencode.json`:
158
116
 
159
117
  ```json
160
118
  {
161
- "CLAUDE_MEM_CHROMA_MODE": "remote"
119
+ "plugin": ["@ephemushroom/opencode-claude-mem"]
162
120
  }
163
121
  ```
164
122
 
165
- Start Chroma server manually:
123
+ Then restart OpenCode.
124
+
125
+ ### Step 3: Verify
166
126
 
167
127
  ```bash
168
- 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
169
129
  ```
170
130
 
171
- > **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.
172
133
 
173
- ### ChromaDB Embedding Model Crash (Bun Runtime)
134
+ ## Usage
174
135
 
175
- After connecting to Chroma, the worker may crash with:
136
+ Once installed, the plugin works automatically:
176
137
 
177
- ```
178
- Collection setup failed: undefined is not an object
179
- (evaluating 'e.cacheDir = ...')
180
- ```
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.
181
146
 
182
- This happens because `@huggingface/transformers` returns `undefined` when imported through Bun's CJS shim.
147
+ ## Memory Search
183
148
 
184
- **Fix — patch `worker-service.cjs`:**
149
+ Memory search is provided by Claude-Mem’s MCP server, not by this plugin.
185
150
 
186
- 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:
187
153
 
188
- ```
189
- ~/.claude/plugins/cache/thedotmack/claude-mem/<version>/scripts/worker-service.cjs
190
- ```
154
+ 1. `search(query="...")`
155
+ 2. `timeline(anchor=ID)`
156
+ 3. `get_observations(ids=[...])`
191
157
 
192
- Find this pattern (minified):
158
+ This plugin focuses on sending memory data to the worker and injecting returned
159
+ context back into OpenCode sessions.
193
160
 
194
- ```javascript
195
- let{env:e}=await Promise.resolve().then(()=>(F9(),N9));
196
- ```
161
+ ## Key Implementation Details
197
162
 
198
- 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.
199
194
 
200
- ```javascript
201
- let{env:e}=await import("@huggingface/transformers");
202
- ```
195
+ ## Troubleshooting
196
+
197
+ ### No memory appears in OpenCode
203
198
 
204
- Then install the dependency:
199
+ - Confirm the worker is running:
205
200
 
206
201
  ```bash
207
- cd ~/.claude/plugins/cache/thedotmack/claude-mem/<version>
208
- bun install
202
+ curl -s http://127.0.0.1:37777/api/health
209
203
  ```
210
204
 
211
- > ⚠️ **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.
212
207
 
213
- ### Verifying ChromaDB Works
208
+ ### OpenCode shows `Worker offline`
214
209
 
215
- ```bash
216
- # Check Chroma server
217
- 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`.
218
212
 
219
- # Check worker health
220
- curl -s http://127.0.0.1:37777/api/health
213
+ ### OpenCode crashes on startup
221
214
 
222
- # Check for sync errors in worker logs
223
- # Open http://localhost:37777 in browser for the web viewer
224
- ```
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.
225
218
 
226
- When working correctly, worker logs should show:
219
+ ### Observations are missing or incomplete
227
220
 
228
- ```
229
- CHROMA_SYNC connected (remote mode, 127.0.0.1:8000)
230
- ```
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.
231
225
 
232
- > **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
233
227
 
234
- ---
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.
235
231
 
236
232
  ## Development
237
233
 
238
- After editing source:
239
-
240
234
  ```bash
235
+ bun install
241
236
  bun run build
242
- # Restart OpenCode to pick up changes
243
- ```
244
-
245
- If using symlink install, changes are picked up after rebuild. If using npm install, bump version and republish.
246
-
247
- ## Architecture
248
-
237
+ bun run lint
238
+ bun run fmt:check
249
239
  ```
250
- OpenCode ←→ Plugin (this repo) ←→ Claude-Mem Worker (port 37777) ←→ SQLite + ChromaDB
251
- ```
252
-
253
- The plugin is a thin HTTP client. All heavy lifting (LLM processing, storage, search) happens in the worker.
254
-
255
- ### Hook Mapping (Claude Code → OpenCode)
256
-
257
- | Claude Code Hook | OpenCode Hook | Purpose |
258
- |-----------------|---------------|---------|
259
- | `SessionStart` → context | `experimental.chat.system.transform` | Inject memory into system prompt (every LLM call, session-level cache) |
260
- | `UserPromptSubmit` → session-init | `chat.message` | Init session with real user prompt |
261
- | `PostToolUse` → observation | `tool.execute.after` | Capture tool executions |
262
- | `Stop` → summarize | `event` (session.idle) | Summarize with real message content |
263
- | `Stop` → session-complete | `event` (session.idle) | Mark session complete |
264
- | *(no equivalent)* | `config` + `command.execute.before` | `/memory` command for user-visible context |
265
-
266
- ### Worker API Endpoints Used
267
-
268
- | Method | Endpoint | Purpose |
269
- |--------|----------|---------|
270
- | `GET` | `/api/health` | Health check |
271
- | `GET` | `/api/context/inject?project={name}` | Get formatted context for system prompt |
272
- | `POST` | `/api/sessions/init` | Initialize session |
273
- | `POST` | `/api/sessions/observations` | Send tool observation |
274
- | `POST` | `/api/sessions/summarize` | Trigger summarization |
275
- | `POST` | `/api/sessions/complete` | Complete session |
276
-
277
- ## Key Implementation Details
278
-
279
- - **Field name**: All worker API calls use `contentSessionId` (not `claudeSessionId`) — using the wrong field name causes silent failures
280
- - **No console output**: All `console.log/warn/error` removed to avoid corrupting OpenCode TUI
281
- - **Deferred toast**: `client.tui.showToast()` is deferred to first hook invocation (TUI not ready during plugin init, calling it crashes OpenCode)
282
- - **Real prompt**: `chat.message` hook extracts actual user input instead of hardcoded "SESSION_START"
283
- - **Real summarize**: `session.idle` fetches last user/assistant messages via `client.session.messages()` for meaningful summaries
284
- - **Context caching**: `getCachedContext()` caches context once per session (invalidated on `session.created`) to avoid redundant worker calls
285
- - **Content-type handling**: Worker returns `text/plain` markdown — `getContext()` checks content-type and uses `response.text()` instead of `response.json()`
286
- - **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
287
- - **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
288
- - **`/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)
289
-
290
- ## Troubleshooting
291
240
 
292
- | Symptom | Cause | Fix |
293
- |---------|-------|-----|
294
- | No toast on startup | Worker not running | Start Claude Code first, or check `curl http://127.0.0.1:37777/api/health` |
295
- | "Worker offline" toast | Worker crashed or not started | Restart Claude Code |
296
- | OpenCode crashes on startup | Plugin calling TUI too early | Update to latest version (deferred toast fix) |
297
- | TUI display corrupted | console.log in plugin code | Ensure no console output in source |
298
- | "SESSION_START" in prompts | Old version bug | Update — `session.created` no longer calls sessionInit |
299
- | Chroma sync failed | Python version or Chroma not running | See [Windows-Specific Setup](#windows-specific-setup) |
300
- | 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.
301
243
 
302
244
  ## License
303
245
 
package/dist/index.d.ts CHANGED
@@ -3,14 +3,13 @@ import { type Plugin } from '@opencode-ai/plugin';
3
3
  * OpenCode Plugin for Claude-Mem
4
4
  *
5
5
  * Hooks used:
6
- * - `config` — register /memory command
7
- * - `command.execute.before` — handle /memory command
8
6
  * - `event` — session lifecycle (session.created, session.idle)
9
7
  * - `tool.execute.after` — capture tool observations
10
8
  * - `experimental.chat.system.transform` — inject memory context into system prompt
11
9
  * - `chat.message` — session init with real user prompt
12
10
  *
13
- * Uses TUI toast notifications for status feedback.
11
+ * Memory context is automatically injected into every conversation via system prompt.
12
+ * No manual commands needed - the plugin works transparently in the background.
14
13
  */
15
14
  export declare const ClaudeMemPlugin: Plugin;
16
15
  declare const _default: {
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();
@@ -109,19 +196,6 @@ var ClaudeMemPlugin = async (ctx) => {
109
196
  });
110
197
  } catch {}
111
198
  }
112
- async function sendStatusMessage(sessionId, text) {
113
- try {
114
- await client.session.prompt({
115
- path: { id: sessionId },
116
- body: {
117
- noReply: true,
118
- parts: [{ type: "text", text, ignored: true }]
119
- }
120
- });
121
- } catch {
122
- await toast(text.slice(0, 200), "info");
123
- }
124
- }
125
199
  let contextCache = undefined;
126
200
  async function getCachedContext() {
127
201
  if (contextCache !== undefined) {
@@ -174,34 +248,6 @@ var ClaudeMemPlugin = async (ctx) => {
174
248
  `).trim();
175
249
  }
176
250
  return {
177
- config: async (input) => {
178
- input.command ??= {};
179
- input.command["memory"] = {
180
- template: "/memory",
181
- description: "Show memory context from Claude-Mem"
182
- };
183
- },
184
- "command.execute.before": async (input) => {
185
- if (input.command !== "memory") {
186
- return;
187
- }
188
- const sessionId = input.sessionID || currentSessionId;
189
- if (!sessionId) {
190
- throw new Error("__MEMORY_NO_SESSION__");
191
- }
192
- const isHealthy = await checkWorkerAndToast();
193
- if (!isHealthy) {
194
- await sendStatusMessage(sessionId, "\u26A0 Claude-Mem worker is offline");
195
- throw new Error("__MEMORY_HANDLED__");
196
- }
197
- const context = await getCachedContext();
198
- if (context) {
199
- await sendStatusMessage(sessionId, context);
200
- } else {
201
- await sendStatusMessage(sessionId, `No memory context available for ${projectName}`);
202
- }
203
- throw new Error("__MEMORY_HANDLED__");
204
- },
205
251
  event: async ({ event }) => {
206
252
  if (event.type === "session.created") {
207
253
  const sessionId = event.properties?.info?.id;
@@ -268,6 +314,25 @@ var ClaudeMemPlugin = async (ctx) => {
268
314
  output.system.push(`<claude-mem-context>
269
315
  [Claude-Mem] Memory Active. Previous Context:
270
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}
271
336
  </claude-mem-context>`);
272
337
  }
273
338
  } catch {}
@@ -277,12 +342,14 @@ ${context}
277
342
  if (!sessionId) {
278
343
  return;
279
344
  }
280
- if (input.tool.startsWith("claude-mem_mcp-search_")) {
345
+ if (shouldSkipObservationTool(input.tool)) {
281
346
  return;
282
347
  }
283
348
  await ensureSessionInit(sessionId);
284
349
  try {
285
- 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);
286
353
  } catch {}
287
354
  }
288
355
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ephemushroom/opencode-claude-mem",
3
- "version": "0.2.3",
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",