@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.
- package/README.md +165 -219
- package/dist/index.js +110 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,57 +1,20 @@
|
|
|
1
1
|
# opencode-claude-mem
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Persistent memory for [OpenCode](https://opencode.ai), powered by
|
|
4
|
+
[Claude-Mem](https://github.com/thedotmack/claude-mem).
|
|
4
5
|
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
+
## Quick Start
|
|
33
15
|
|
|
34
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
### Worker API Endpoints Used
|
|
127
83
|
|
|
128
|
-
|
|
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
|
-
|
|
95
|
+
### Prerequisites
|
|
135
96
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
102
|
+
### Step 1: Install Claude-Mem
|
|
141
103
|
|
|
142
|
-
|
|
104
|
+
In Claude Code:
|
|
143
105
|
|
|
144
|
-
|
|
106
|
+
```text
|
|
107
|
+
/plugin marketplace add thedotmack/claude-mem
|
|
108
|
+
/plugin install claude-mem
|
|
109
|
+
```
|
|
145
110
|
|
|
146
|
-
|
|
111
|
+
Restart Claude Code so the worker can start and initialize its data directory.
|
|
147
112
|
|
|
148
|
-
|
|
149
|
-
# Install Python ChromaDB CLI
|
|
150
|
-
uv tool install chromadb --python 3.13
|
|
151
|
-
```
|
|
113
|
+
### Step 2: Add the OpenCode Plugin
|
|
152
114
|
|
|
153
|
-
|
|
115
|
+
Add this plugin to your project or global `opencode.json`:
|
|
154
116
|
|
|
155
117
|
```json
|
|
156
118
|
{
|
|
157
|
-
"
|
|
119
|
+
"plugin": ["@ephemushroom/opencode-claude-mem"]
|
|
158
120
|
}
|
|
159
121
|
```
|
|
160
122
|
|
|
161
|
-
|
|
123
|
+
Then restart OpenCode.
|
|
124
|
+
|
|
125
|
+
### Step 3: Verify
|
|
162
126
|
|
|
163
127
|
```bash
|
|
164
|
-
|
|
128
|
+
curl -s http://127.0.0.1:37777/api/health
|
|
165
129
|
```
|
|
166
130
|
|
|
167
|
-
|
|
131
|
+
If the worker is healthy, OpenCode should show a toast like
|
|
132
|
+
`Memory active · <project>` when a session starts.
|
|
168
133
|
|
|
169
|
-
|
|
134
|
+
## Usage
|
|
170
135
|
|
|
171
|
-
|
|
136
|
+
Once installed, the plugin works automatically:
|
|
172
137
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
147
|
+
## Memory Search
|
|
179
148
|
|
|
180
|
-
|
|
149
|
+
Memory search is provided by Claude-Mem’s MCP server, not by this plugin.
|
|
181
150
|
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
154
|
+
1. `search(query="...")`
|
|
155
|
+
2. `timeline(anchor=ID)`
|
|
156
|
+
3. `get_observations(ids=[...])`
|
|
187
157
|
|
|
188
|
-
|
|
158
|
+
This plugin focuses on sending memory data to the worker and injecting returned
|
|
159
|
+
context back into OpenCode sessions.
|
|
189
160
|
|
|
190
|
-
|
|
191
|
-
let{env:e}=await Promise.resolve().then(()=>(F9(),N9));
|
|
192
|
-
```
|
|
161
|
+
## Key Implementation Details
|
|
193
162
|
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
## Troubleshooting
|
|
196
|
+
|
|
197
|
+
### No memory appears in OpenCode
|
|
199
198
|
|
|
200
|
-
|
|
199
|
+
- Confirm the worker is running:
|
|
201
200
|
|
|
202
201
|
```bash
|
|
203
|
-
|
|
204
|
-
bun install
|
|
202
|
+
curl -s http://127.0.0.1:37777/api/health
|
|
205
203
|
```
|
|
206
204
|
|
|
207
|
-
|
|
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
|
-
###
|
|
208
|
+
### OpenCode shows `Worker offline`
|
|
210
209
|
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
curl -s http://127.0.0.1:37777/api/health
|
|
213
|
+
### OpenCode crashes on startup
|
|
217
214
|
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
219
|
+
### Observations are missing or incomplete
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
345
|
+
if (shouldSkipObservationTool(input.tool)) {
|
|
240
346
|
return;
|
|
241
347
|
}
|
|
242
348
|
await ensureSessionInit(sessionId);
|
|
243
349
|
try {
|
|
244
|
-
|
|
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
|
};
|