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