@ephemushroom/opencode-claude-mem 0.4.2 → 0.4.4

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
@@ -3,9 +3,19 @@
3
3
  Persistent memory for [OpenCode](https://opencode.ai), powered by
4
4
  [Claude-Mem](https://github.com/thedotmack/claude-mem).
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
+ Share the same Claude-Mem worker, database, and memory across your coding
7
+ agents: memories written by Claude Code are visible to OpenCode, and vice
8
+ versa. Previous observations and summaries are injected into new OpenCode
9
+ sessions automatically.
10
+
11
+ ```mermaid
12
+ flowchart LR
13
+ CC[Claude Code] -->|writes as<br/>platform_source=claude| W
14
+ OC[OpenCode<br/>+ this plugin] -->|writes as<br/>platform_source=opencode| W
15
+ W[Claude-Mem Worker<br/>127.0.0.1:37777] --> DB[(SQLite +<br/>ChromaDB)]
16
+ W -->|shared project memory| CC
17
+ W -->|shared project memory| OC
18
+ ```
9
19
 
10
20
  > **Note:** This plugin is a thin OpenCode adapter for an existing Claude-Mem
11
21
  > installation. It does **not** install Claude-Mem, manage slash commands, or
@@ -23,106 +33,117 @@ into new OpenCode sessions automatically.
23
33
  ```
24
34
 
25
35
  3. Restart OpenCode.
26
- 4. Start a session — memory context will be injected automatically when the
27
- Claude-Mem worker is available.
28
-
29
- ## Key Features
30
-
31
- - **Shared Memory** — Uses the same Claude-Mem worker and memory store as
32
- Claude Code.
33
- - **Auto-Start** — When the plugin loads and the Claude-Mem worker is not
34
- already running, it spawns `bunx claude-mem start` once per OpenCode process
35
- (skipped silently if `bun` is not on `PATH` or the worker is already up).
36
- - **Automatic Context Injection** — Injects relevant project memory into the
37
- system prompt for new OpenCode turns.
38
- - **Compaction Support** — Re-injects memory context during OpenCode session
39
- compaction so long conversations keep their historical context.
40
- - **Observation Capture** — Sends tool observations to Claude-Mem for future
41
- retrieval and summarization.
42
- - **Native Memory Tools** — Exposes `mem-search`, `mem-timeline`, and
43
- `mem-get-observations` OpenCode tools backed by the Claude-Mem worker API,
44
- covering the full search → timeline → details workflow without the Claude
45
- Code compatibility bridge or an MCP server.
46
- - **Sidebar Status** — Registers a TUI sidebar panel ("Memory") showing worker
47
- health, project, observation/summary/session counts, and processing queue
48
- depth, refreshed every few seconds.
49
- - **Observation Hardening** — Skips low-value meta tools, strips
50
- `<claude-mem-context>` and `<private>` tags before storage, and truncates
51
- oversized observation payloads by UTF-8 byte size.
52
- - **Graceful Degradation** — If the worker is offline and cannot be started,
53
- the plugin fails open and OpenCode continues to work normally.
36
+ 4. Start a session — memory context is injected automatically, the `mem-*`
37
+ tools become available, and a collapsible **Memory** section appears in the
38
+ sidebar.
54
39
 
55
- ## How It Works
40
+ Using [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent)?
41
+ See [Disable the Claude Code bridge](#using-with-oh-my-openagent-disable-the-claude-code-bridge)
42
+ to avoid running two claude-mem integrations at once.
56
43
 
57
- ```
58
- OpenCode session
59
- |
60
- |-- plugin loads
61
- | '-- connect to Claude-Mem worker from env/settings/default endpoint
62
- |
63
- |-- tool(mem-search) ............... search memory via worker API
64
- |-- session.created ................ track session + reset cache
65
- |-- chat.message ................... init session with real user prompt
66
- |-- tool.execute.after ............. send tool observation
67
- |-- system.transform ............... inject memory into system prompt
68
- |-- session.compacting ............. preserve memory during compaction
69
- |-- message.updated ................ capture assistant text (debounced)
70
- |-- file.edited .................... record file edit observation
71
- |-- session.compacted .............. summarize after compaction
72
- |-- session.idle ................... flush + summarize
73
- '-- session.deleted ................ flush + complete (no zombie sessions)
74
- ```
44
+ ## What You Get
75
45
 
76
- The plugin is intentionally small. It only adapts OpenCode hook events to the
77
- Claude-Mem worker HTTP API. All indexing, summarization, memory search, and
78
- storage stay in upstream Claude-Mem.
46
+ | Surface | What it looks like |
47
+ |---|---|
48
+ | **System prompt** | `<claude-mem-context>` block with recent observations + session summaries for the current project |
49
+ | **Tools** | `mem-search` → `mem-timeline` → `mem-get-observations` (full search workflow, no MCP server needed) |
50
+ | **Sidebar** | `▶ Memory (online, 11.7k obs)` — click to expand recent sessions and latest observations |
51
+ | **Background** | Every tool call, assistant message, and file edit captured as observations; sessions summarized on idle |
79
52
 
80
- ## Architecture
53
+ ### Sidebar
81
54
 
82
- ```
83
- OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker <-> SQLite + ChromaDB
55
+ The plugin registers a sidebar section styled after OpenCode's native
56
+ MCP/Context sections — borderless, click-to-toggle:
57
+
58
+ ```text
59
+ ▶ Memory (online, 11.7k obs) ← collapsed (default), single line
84
60
  ```
85
61
 
86
- The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
87
- `CLAUDE_MEM_WORKER_PORT` environment variables, then
88
- `~/.claude-mem/settings.json`, then `127.0.0.1:37777`.
62
+ ```text
63
+ ▼ Memory ← click header to expand
64
+ • obs 11.7k · sum 2311 · ses 1186
65
+ Recent sessions
66
+ • 实现 tui.json 自动注册(self-heal)机制…
67
+ • 设计 Memory 面板:跨工具记忆共享…
68
+ Latest
69
+ ◆ Memory 侧边栏完整通过 CI 流水线…
70
+ ● 修复点击不展开:改用 solid-js signal…
71
+ ⚖ 计划按原生风格重构 Memory 面板…
72
+ ```
89
73
 
90
- ### Hook Mapping
74
+ - Summary line turns **yellow** while the worker is processing (`(queue N)`)
75
+ and **red** when offline (`(offline)`).
76
+ - Observation icons match the injected context legend: ◆ feature · ● bugfix ·
77
+ ⚖ decision · ○ discovery · ↻ refactor · ✓ change · ⚠/⚷ security.
78
+ - Recent items are only fetched while expanded, keeping the collapsed poll
79
+ loop cheap (stats every 5s).
80
+ - Fails open: worker offline → shows the offline state, never blocks the TUI.
81
+
82
+ The sidebar loads via the package's `./tui` export. On startup the plugin
83
+ **self-heals `~/.config/opencode/tui.json`**: if it is registered as a server
84
+ plugin in `opencode.json` but missing from the TUI plugin list, it appends
85
+ itself — no manual configuration. Symlinked `tui.json` files are written
86
+ through, preserving dotfiles setups.
87
+
88
+ ### Memory Tools
89
+
90
+ Three native OpenCode tools cover the same 3-step workflow as the upstream
91
+ Claude-Mem MCP server — no MCP server or stdio subprocess required:
92
+
93
+ ```mermaid
94
+ flowchart LR
95
+ A["mem-search<br/><i>find IDs by query</i>"] --> B["mem-timeline<br/><i>context around an ID</i>"]
96
+ B --> C["mem-get-observations<br/><i>full details for IDs</i>"]
97
+ ```
91
98
 
92
- | Claude Code | OpenCode plugin | Purpose |
99
+ | Tool | Worker endpoint | Use it for |
93
100
  |---|---|---|
94
- | `SessionStart` | `experimental.chat.system.transform` | Inject memory context |
95
- | `SessionStart` | `experimental.session.compacting` | Preserve memory during compaction |
96
- | `UserPromptSubmit` | `chat.message` | Initialize session with real user prompt |
97
- | `PostToolUse` | `tool.execute.after` | Capture tool observations |
98
- | Claude-Mem MCP `search` | `tool` (`mem-search`) | Search memory from OpenCode |
99
- | Claude-Mem MCP `timeline` | `tool` (`mem-timeline`) | Chronological context around an observation |
100
- | Claude-Mem MCP `get_observations` | `tool` (`mem-get-observations`) | Fetch full observation details by ID |
101
- | _(streaming)_ | `event` (`message.updated`) | Capture assistant text (debounced) |
102
- | _(streaming)_ | `event` (`file.edited`) | Record file edit observations |
103
- | _(compaction)_ | `event` (`session.compacted`) | Summarize after OpenCode compacts |
104
- | `Stop` | `event` (`session.idle`) | Flush + summarize |
105
- | `SessionEnd` | `event` (`session.deleted`) | Flush + complete (no zombie active rows) |
101
+ | `mem-search` | `GET /api/search` | Formatted index with query, project, source, type, date, pagination, and ordering filters |
102
+ | `mem-timeline` | `GET /api/timeline` | Chronological records around an `anchor` ID (or auto-located via `query`) |
103
+ | `mem-get-observations` | `POST /api/observations/batch` | Full details for IDs — e.g. the IDs shown in the injected context |
106
104
 
107
- ### Worker API Endpoints Used
105
+ ## How It Works
108
106
 
109
- | Method | Endpoint | Purpose |
110
- |---|---|---|
111
- | `GET` | `/api/health` | Health check |
112
- | `GET` | `/api/context/inject?project={name}` | Fetch formatted memory context |
113
- | `POST` | `/api/sessions/init` | Initialize session |
114
- | `POST` | `/api/sessions/observations` | Store tool observation |
115
- | `POST` | `/api/sessions/summarize` | Trigger summarization |
116
- | `POST` | `/api/sessions/complete` | Complete session |
117
- | `GET` | `/api/search?query={query}&project={name}` | Search memory |
118
- | `GET` | `/api/timeline?project={name}&anchor={id}` | Chronological context around an observation |
119
- | `POST` | `/api/observations/batch` | Fetch full observation details by ID |
120
- | `GET` | `/api/stats` + `/api/processing-status` | Sidebar status panel |
121
- | `GET` | `/api/summaries` + `/api/observations` | Sidebar recent items (expanded only) |
107
+ ```mermaid
108
+ sequenceDiagram
109
+ participant OC as OpenCode
110
+ participant P as Plugin
111
+ participant W as Claude-Mem Worker
112
+
113
+ OC->>P: plugin loads
114
+ P->>P: self-heal tui.json
115
+ P->>W: health check (auto-start via bunx if down)
116
+ OC->>P: chat.message (first user prompt)
117
+ P->>W: POST /api/sessions/init
118
+ OC->>P: system.transform
119
+ W-->>P: GET /api/context/inject (cached per session)
120
+ P-->>OC: inject <claude-mem-context> into system prompt
121
+ loop during the session
122
+ OC->>P: tool.execute.after / message.updated / file.edited
123
+ P->>W: POST /api/sessions/observations
124
+ end
125
+ OC->>P: session.idle / session.compacted
126
+ P->>W: POST /api/sessions/summarize
127
+ OC->>P: session.deleted
128
+ P->>W: POST /api/sessions/complete
129
+ ```
122
130
 
123
- Session and observation writes include `platformSource: "opencode"` so
124
- Claude-Mem can attribute new OpenCode memories separately from Claude Code,
125
- while context injection and `mem-search` still search the shared project memory.
131
+ The plugin is intentionally small: it only adapts OpenCode hook events to the
132
+ Claude-Mem worker HTTP API. All indexing, summarization, memory search, and
133
+ storage stay in upstream Claude-Mem.
134
+
135
+ ### Cross-Tool Memory Sharing
136
+
137
+ Writes are attributed (`platformSource: "opencode"`), reads are shared:
138
+
139
+ | Operation | Behavior |
140
+ |---|---|
141
+ | OpenCode writes | Stored as `platform_source=opencode` |
142
+ | Claude Code writes | Stored as `platform_source=claude` |
143
+ | Either reads (inject/search) | Sees **all** memory for the project, regardless of source |
144
+
145
+ The sharing key is the **project name** (worktree directory name) — work in
146
+ the same project directory and memory flows both ways.
126
147
 
127
148
  ## Installation
128
149
 
@@ -131,8 +152,7 @@ while context injection and `mem-search` still search the shared project memory.
131
152
  - [Claude Code](https://claude.com/claude-code) with
132
153
  [Claude-Mem](https://github.com/thedotmack/claude-mem) installed
133
154
  - [OpenCode](https://opencode.ai) with plugin support
134
- - A running Claude-Mem worker (default `127.0.0.1:37777`, or your
135
- `CLAUDE_MEM_WORKER_HOST` / `CLAUDE_MEM_WORKER_PORT` settings)
155
+ - A running Claude-Mem worker (default `127.0.0.1:37777`)
136
156
 
137
157
  ### Step 1: Install Claude-Mem
138
158
 
@@ -163,124 +183,115 @@ Then restart OpenCode.
163
183
  curl -s http://127.0.0.1:37777/api/health
164
184
  ```
165
185
 
166
- If the worker is healthy, OpenCode should show a toast like
167
- `Memory active · <project>` when a session starts.
168
-
169
- ## Usage
170
-
171
- Once installed, the plugin works automatically:
172
-
173
- - **Context injection** — Memory is injected into the system prompt on each LLM
174
- call, with session-level caching to avoid repeated worker requests.
175
- - **Compaction preservation** — The same cached memory context is pushed into
176
- OpenCode’s compaction path so memory survives conversation compression.
177
- - **Tool observation capture** — Tool executions are stored as observations,
178
- except for low-value/meta tools and Claude-Mem search tools.
179
- - **Memory search** — The `mem-search` tool queries the worker's `/api/search`
180
- endpoint for the current project.
181
- - **Assistant message capture** — Assistant text from `message.updated` is
182
- buffered with 250ms debounce per session and sent as a single
183
- `assistant_message` observation per turn (avoids streaming-chunk floods).
184
- - **File edit capture** — `file.edited` events are forwarded as `file_edit`
185
- observations (path only; OpenCode does not include diff in the event).
186
- - **Session summarization** — On `session.compacted` and `session.idle`, the
187
- plugin flushes any pending assistant buffer, fetches the latest user and
188
- assistant messages, and asks Claude-Mem to summarize them.
189
- - **Session lifecycle hygiene** — On `session.deleted` the plugin tells the
190
- worker to `completeSession`, preventing zombie `'active'` rows from
191
- accumulating stale `pending_messages` (the "queueDepth never decreases"
192
- failure mode).
193
-
194
- ## Memory Tools
195
-
196
- This plugin provides three native OpenCode tools backed by the Claude-Mem
197
- worker's HTTP API. They cover the same search → timeline → details workflow as
198
- the upstream Claude-Mem MCP server, without requiring an MCP server or the
199
- Claude Code compatibility bridge:
200
-
201
- - **`mem-search`** — `GET /api/search`. Returns a formatted index of matching
202
- observations (with IDs) for the current project.
203
- - **`mem-timeline`** — `GET /api/timeline`. Given an observation ID as `anchor`
204
- (or a `query` to locate one), returns the chronological records around it.
205
- - **`mem-get-observations`** — `POST /api/observations/batch`. Fetches full
206
- observation details for a list of IDs (e.g. IDs surfaced in the injected
207
- memory context or by `mem-search`).
208
-
209
- Claude Code's claude-mem plugin also registers an MCP server through its own
210
- `.mcp.json`. OpenCode does not automatically load Claude Code plugin MCP
211
- servers. If you previously saw Claude-Mem MCP tools in OpenCode, they were most
212
- likely supplied by a Claude Code compatibility bridge such as oh-my-openagent.
213
- Disabling that bridge for `claude-mem@thedotmack` removes those bridged MCP
214
- tools; the three `mem-*` tools above are the native replacements inside this
215
- plugin, so a separately configured MCP server is not required.
216
-
217
- ## Sidebar Status
218
-
219
- When running in the OpenCode TUI, the plugin registers a sidebar section titled
220
- **Memory** via its `./tui` export, styled after OpenCode's native MCP/Context
221
- sections: a borderless, click-to-toggle collapsible block.
222
-
223
- - **Collapsed (default)** — a single line: `▶ Memory (online, 11.7k obs)`.
224
- The summary turns yellow when the worker is processing (`(queue N)`) and red
225
- when it is offline (`(offline)`).
226
- - **Expanded** — click the header to expand: database counts, processing
227
- status, the last 3 session summaries (`Recent sessions`), and the last 3
228
- observations (`Latest`, with the same type icons used in the injected
229
- context: ◆ feature, ● bugfix, ⚖ decision, ○ discovery, …).
230
-
231
- Recent items are only fetched while expanded, so the collapsed poll loop stays
232
- cheap. The panel fails open — if the worker is offline it simply shows the
233
- offline state and never blocks the TUI. Requires OpenCode's `@opentui/solid`
234
- runtime; if that is unavailable the panel is skipped silently.
235
-
236
- The plugin also self-heals `~/.config/opencode/tui.json` on load (like
237
- oh-my-openagent does): if the plugin is registered as a server plugin in
238
- `opencode.json` but missing from the TUI plugin list, it appends itself so the
239
- sidebar loads without manual configuration. Symlinked `tui.json` files are
240
- written through, preserving dotfiles setups.
241
-
242
- ## Key Implementation Details
186
+ If the worker is healthy, OpenCode shows a toast like
187
+ `Memory active · <project>` when a session starts, and the sidebar shows
188
+ `▶ Memory (online, … obs)`.
189
+
190
+ ## Using with oh-my-openagent: Disable the Claude Code Bridge
191
+
192
+ oh-my-openagent ships a Claude Code compatibility layer that can load Claude
193
+ Code plugins — including `claude-mem@thedotmack` — inside OpenCode. Running
194
+ that bridge **and** this native plugin at the same time means two integrations
195
+ write to the same worker:
196
+
197
+ - duplicate observations for every tool call
198
+ - duplicate context injection and toasts
199
+ - bridged MCP tools (`search`, `timeline`, `get_observations`) shadowing the
200
+ native `mem-*` tools
201
+
202
+ Disable the bridge for claude-mem in `~/.config/opencode/oh-my-openagent.jsonc`:
203
+
204
+ ```jsonc
205
+ {
206
+ "claude_code": {
207
+ "plugins_override": {
208
+ "claude-mem@thedotmack": false
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ This only disables the *bridged* claude-mem inside OpenCode. Claude Code
215
+ itself keeps using claude-mem normally, and memory stays shared through the
216
+ worker. Everything the bridge provided is covered natively by this plugin:
217
+
218
+ | Bridged (before) | Native (this plugin) |
219
+ |---|---|
220
+ | MCP `search` | `mem-search` tool |
221
+ | MCP `timeline` | `mem-timeline` tool |
222
+ | MCP `get_observations` | `mem-get-observations` tool |
223
+ | `SessionStart` context hook | `system.transform` injection |
224
+ | `PostToolUse` observation hook | `tool.execute.after` capture |
225
+
226
+ ## Reference
227
+
228
+ ### Hook Mapping
229
+
230
+ | Claude Code | OpenCode plugin | Purpose |
231
+ |---|---|---|
232
+ | `SessionStart` | `experimental.chat.system.transform` | Inject memory context |
233
+ | `SessionStart` | `experimental.session.compacting` | Preserve memory during compaction |
234
+ | `UserPromptSubmit` | `chat.message` | Initialize session with real user prompt |
235
+ | `PostToolUse` | `tool.execute.after` | Capture tool observations |
236
+ | Claude-Mem MCP `search` | `tool` (`mem-search`) | Search memory from OpenCode |
237
+ | Claude-Mem MCP `timeline` | `tool` (`mem-timeline`) | Chronological context around an observation |
238
+ | Claude-Mem MCP `get_observations` | `tool` (`mem-get-observations`) | Fetch full observation details by ID |
239
+ | _(streaming)_ | `event` (`message.updated`) | Capture assistant text (debounced 250ms) |
240
+ | _(streaming)_ | `event` (`file.edited`) | Record file edit observations |
241
+ | _(compaction)_ | `event` (`session.compacted`) | Summarize after OpenCode compacts |
242
+ | `Stop` | `event` (`session.idle`) | Flush + summarize |
243
+ | `SessionEnd` | `event` (`session.deleted`) | Flush + complete (no zombie active rows) |
244
+
245
+ ### Worker API Endpoints Used
246
+
247
+ | Method | Endpoint | Purpose |
248
+ |---|---|---|
249
+ | `GET` | `/api/health` | Health check |
250
+ | `GET` | `/api/context/inject?project={name}` | Fetch formatted memory context |
251
+ | `POST` | `/api/sessions/init` | Initialize session |
252
+ | `POST` | `/api/sessions/observations` | Store tool observation |
253
+ | `POST` | `/api/sessions/summarize` | Trigger summarization |
254
+ | `POST` | `/api/sessions/complete` | Complete session |
255
+ | `GET` | `/api/search?query=...&project=...&dateStart=...&dateEnd=...` | `mem-search`; also supports `limit`, `platformSource`, `type`, `obs_type`, `offset`, and `orderBy` |
256
+ | `GET` | `/api/timeline?project={name}&anchor={id}` | `mem-timeline` |
257
+ | `POST` | `/api/observations/batch` | `mem-get-observations` |
258
+ | `GET` | `/api/stats` + `/api/processing-status` | Sidebar status |
259
+ | `GET` | `/api/summaries` + `/api/observations` | Sidebar recent items (expanded only) |
260
+
261
+ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
262
+ `CLAUDE_MEM_WORKER_PORT` environment variables → `~/.claude-mem/settings.json`
263
+ → `127.0.0.1:37777`.
264
+
265
+ ### Key Implementation Details
243
266
 
244
267
  - **Thin client architecture** — `src/index.ts` handles OpenCode hooks and
245
- session state; `src/worker-client.ts` is a static HTTP client.
246
- - **No console logging** — The plugin never writes to `console.*` because that
247
- can corrupt the OpenCode TUI.
248
- - **Deferred toast** — Health toasts only happen after hook execution begins,
268
+ session state; `src/worker-client.ts` is a static HTTP client; `src/tui.ts`
269
+ renders the sidebar; `src/tui-registration.ts` self-heals `tui.json`.
270
+ - **Zero runtime dependencies** — the OpenCode plugin SDK is bundled into
271
+ `dist/`, keeping the install footprint under ~500 KB.
272
+ - **Reactive sidebar** — collapse state and view data are solid-js signals
273
+ (shared with OpenCode's own solid instance via `--external solid-js`), so
274
+ clicking the header re-renders reliably; falls back to plain closures if
275
+ solid-js cannot be resolved.
276
+ - **No console logging** — `console.*` output corrupts the OpenCode TUI;
277
+ the plugin never logs and never throws from hooks.
278
+ - **Deferred toast** — health toasts only happen after hook execution begins,
249
279
  avoiding startup crashes caused by early TUI access.
250
- - **Real prompt initialization** — `chat.message` sends the actual user prompt
251
- to Claude-Mem instead of a synthetic placeholder.
252
- - **OpenCode attribution** — Session, observation, summarize, and complete
253
- payloads include `platformSource: "opencode"`.
254
- - **Worker endpoint resolution** — The plugin reads `CLAUDE_MEM_WORKER_HOST` /
255
- `CLAUDE_MEM_WORKER_PORT`, then `~/.claude-mem/settings.json`, then defaults to
256
- `127.0.0.1:37777`.
257
- - **Real summarize payloads** — On `session.idle`, the plugin reads the latest
258
- session messages and sends the last user and assistant messages for summary.
259
- - **Context caching** — Memory context is fetched once per session and reused
280
+ - **Auto-start** — if the worker is down on load, spawns
281
+ `bunx claude-mem start` once per OpenCode process (skipped if `bun` is not
282
+ on `PATH`).
283
+ - **Context caching** — memory context is fetched once per session and reused
260
284
  across prompt injection and compaction.
261
- - **Circular memory protection** — Injected context is wrapped in
262
- `<claude-mem-context>` tags, Claude-Mem MCP search tools are skipped, and
263
- memory-related tags are stripped before storing observations.
264
- - **UTF-8 truncation** — Large observation outputs are capped by byte size to
265
- reduce token waste and avoid oversize worker payloads.
266
- - **Field name correctness** — Worker payloads use `contentSessionId`, not
267
- `claudeSessionId`.
268
- - **PreToolUse file context** — Claude Code's native `PreToolUse Read`
269
- `file-context` hook is not mirrored because the current OpenCode plugin
270
- `tool.execute.before` API only lets plugins alter tool args; it has no channel
271
- to inject extra LLM context before the tool call.
272
-
273
- ## Differences from `bloodf/opencode-mem`
274
-
275
- This project intentionally stays smaller in scope.
276
-
277
- - **This plugin does**: bridge OpenCode hooks to an already-installed
278
- Claude-Mem worker.
279
- - **This plugin does not**: auto-install Claude-Mem, auto-edit OpenCode config,
280
- auto-copy skills, auto-register slash commands, or auto-start the worker.
281
-
282
- That keeps the runtime behavior predictable and leaves worker ownership with the
283
- upstream Claude-Mem installation.
285
+ - **Circular memory protection** — injected context is wrapped in
286
+ `<claude-mem-context>` tags, Claude-Mem search tools are skipped from
287
+ observation capture, and memory tags are stripped before storage.
288
+ - **Observation hardening** — low-value meta tools skipped; oversized payloads
289
+ truncated by UTF-8 byte size (24 KB cap).
290
+ - **Field name correctness** — worker payloads use `contentSessionId`, not
291
+ `claudeSessionId` (the wrong name fails silently).
292
+ - **Session lifecycle hygiene** — `session.deleted` triggers
293
+ `completeSession`, preventing zombie `active` rows from accumulating stale
294
+ `pending_messages`.
284
295
 
285
296
  ## Troubleshooting
286
297
 
@@ -297,49 +308,52 @@ curl -s http://127.0.0.1:37777/api/health
297
308
 
298
309
  ### OpenCode shows `Worker offline`
299
310
 
300
- The plugin will try to launch the worker via `bunx claude-mem start` once on
301
- plugin load. If that toast still appears:
311
+ The plugin tries to launch the worker via `bunx claude-mem start` once on
312
+ plugin load. If the toast still appears:
302
313
 
303
314
  - Confirm `bun` is on your `PATH` — `bun --version` should print a version.
304
- - Confirm `claude-mem` is installed for `bunx`/`npx` — run
315
+ - Confirm `claude-mem` is installed for `bunx` — run
305
316
  `bunx claude-mem --version` once to populate the cache.
306
317
  - On Windows after a forced kill, port `37777` may stay in `TIME_WAIT` for
307
318
  30-120 seconds; wait it out or restart Claude Code.
308
319
  - Restart Claude Code to bring Claude-Mem back up via its own supervisor.
309
- - Verify the worker port is still `37777`.
310
320
 
311
- ### OpenCode crashes on startup
321
+ ### Sidebar Memory section is missing
312
322
 
313
- - Update to a version with deferred TUI access.
314
- - Ensure there are no `console.log`, `console.warn`, or `console.error` calls in
315
- local plugin modifications.
323
+ - Check `~/.config/opencode/tui.json` contains this plugin in its `plugin`
324
+ array — the plugin self-heals this file on load, so restarting OpenCode
325
+ twice (once to heal, once to load) fixes a missing entry.
326
+ - The sidebar requires OpenCode's `@opentui/solid` runtime; if unavailable the
327
+ section is skipped silently while hooks and tools keep working.
316
328
 
317
- ### Observations are missing or incomplete
329
+ ### Clicking the Memory header does nothing
330
+
331
+ - Upgrade to ≥ 0.4.2 — earlier versions used non-reactive state and the
332
+ toggle never re-rendered.
318
333
 
319
- - Ensure the worker API payload uses `contentSessionId`.
320
- - Be aware that low-value meta tools and Claude-Mem search tools are skipped by
321
- design.
322
- - Very large tool outputs are truncated before storage.
334
+ ### Duplicate observations / duplicate toasts
323
335
 
324
- ### Memory tools are unavailable
336
+ - You are likely running both this plugin and a Claude Code compatibility
337
+ bridge for claude-mem. See
338
+ [Disable the Claude Code bridge](#using-with-oh-my-openagent-disable-the-claude-code-bridge).
339
+
340
+ ### Observations are missing or incomplete
325
341
 
326
- - Use the built-in `mem-search`, `mem-timeline`, and `mem-get-observations`
327
- tools exposed by this plugin — they cover the full Claude-Mem search workflow
328
- natively, so a separate MCP server is not required.
329
- - If the tools return `worker is offline`, confirm the worker health endpoint
330
- responds (see above).
342
+ - Low-value meta tools and Claude-Mem search tools are skipped by design.
343
+ - Very large tool outputs are truncated before storage (24 KB).
331
344
 
332
345
  ## Development
333
346
 
334
347
  ```bash
335
348
  bun install
336
- bun run build
337
- bun run lint
338
- bun run fmt:check
349
+ bun run build # bundle dist/index.js + dist/tui.js + emit declarations
350
+ bun test # bun's built-in runner
351
+ bun run lint # oxlint
352
+ bun run fmt:check # oxfmt
339
353
  ```
340
354
 
341
- If you edit source code locally, rebuild and restart OpenCode to pick up the new
342
- plugin bundle.
355
+ If you edit source code locally, rebuild and restart OpenCode to pick up the
356
+ new plugin bundle.
343
357
 
344
358
  ## License
345
359
 
package/dist/index.js CHANGED
@@ -12381,6 +12381,27 @@ function parseWorkerEndpoint(env, rawSettings) {
12381
12381
  const port = readPort(env["CLAUDE_MEM_WORKER_PORT"]) ?? readPort(settings["CLAUDE_MEM_WORKER_PORT"]) ?? DEFAULT_WORKER_PORT;
12382
12382
  return { host, port };
12383
12383
  }
12384
+ function buildSearchParams(options) {
12385
+ const params = new URLSearchParams;
12386
+ const entries = [
12387
+ ["query", options.query ?? ""],
12388
+ ["limit", options.limit],
12389
+ ["project", options.project],
12390
+ ["platformSource", options.platformSource],
12391
+ ["type", options.type],
12392
+ ["obs_type", options.obs_type],
12393
+ ["dateStart", options.dateStart],
12394
+ ["dateEnd", options.dateEnd],
12395
+ ["offset", options.offset],
12396
+ ["orderBy", options.orderBy]
12397
+ ];
12398
+ for (const [key, value] of entries) {
12399
+ if (value !== undefined) {
12400
+ params.set(key, String(value));
12401
+ }
12402
+ }
12403
+ return params;
12404
+ }
12384
12405
  function readSettingsRaw() {
12385
12406
  try {
12386
12407
  return readFileSync(SETTINGS_FILE, "utf8");
@@ -12692,12 +12713,9 @@ class WorkerClient {
12692
12713
  return [];
12693
12714
  }
12694
12715
  }
12695
- static async search(query, project, limit) {
12716
+ static async search(options) {
12696
12717
  try {
12697
- const params = new URLSearchParams({ query, project });
12698
- if (limit !== undefined) {
12699
- params.set("limit", String(limit));
12700
- }
12718
+ const params = buildSearchParams(options);
12701
12719
  const response = await fetch(`${this.BASE_URL}/api/search?${params.toString()}`);
12702
12720
  if (!response.ok) {
12703
12721
  return "";
@@ -12972,7 +12990,10 @@ var ClaudeMemPlugin = async (ctx) => {
12972
12990
  return false;
12973
12991
  }
12974
12992
  try {
12975
- await WorkerClient.sessionInit(sessionId, projectName, prompt || "SESSION_START");
12993
+ const result = await WorkerClient.sessionInit(sessionId, projectName, prompt || "SESSION_START");
12994
+ if (!result) {
12995
+ return false;
12996
+ }
12976
12997
  initializedSessions.add(sessionId);
12977
12998
  currentSessionId = sessionId;
12978
12999
  return true;
@@ -13018,18 +13039,48 @@ var ClaudeMemPlugin = async (ctx) => {
13018
13039
  return {
13019
13040
  tool: {
13020
13041
  "mem-search": tool({
13021
- description: "Search Claude-Mem persistent memory for this OpenCode project.",
13042
+ description: "Search Claude-Mem persistent memory. Supports query, project, platformSource, type, obs_type, dateStart, dateEnd, offset, and orderBy filters.",
13022
13043
  args: {
13023
- query: tool.schema.string().min(1).describe("Search query for Claude-Mem memory"),
13024
- limit: tool.schema.number().int().positive().max(50).optional().describe("Maximum number of search results")
13044
+ query: tool.schema.string().min(1).optional().describe("Optional semantic search query for Claude-Mem memory"),
13045
+ limit: tool.schema.number().int().positive().max(100).optional().describe("Maximum number of search results (default 20)"),
13046
+ project: tool.schema.string().min(1).optional().describe("Filter by project name"),
13047
+ platformSource: tool.schema.string().min(1).optional().describe("Filter by platform source, such as claude or opencode"),
13048
+ type: tool.schema.string().min(1).optional().describe("Filter by result type"),
13049
+ obs_type: tool.schema.string().min(1).optional().describe("Filter by observation type, such as feature or bugfix"),
13050
+ dateStart: tool.schema.string().min(1).optional().describe("Start date filter in ISO 8601 or YYYY-MM-DD format"),
13051
+ dateEnd: tool.schema.string().min(1).optional().describe("End date filter in ISO 8601 or YYYY-MM-DD format"),
13052
+ offset: tool.schema.number().int().min(0).optional().describe("Pagination offset"),
13053
+ orderBy: tool.schema.enum(["date_desc", "date_asc", "relevance"]).optional().describe("Sort order")
13025
13054
  },
13026
- execute: async ({ query, limit }) => {
13055
+ execute: async ({
13056
+ query,
13057
+ limit,
13058
+ project: projectFilter,
13059
+ platformSource,
13060
+ type,
13061
+ obs_type,
13062
+ dateStart,
13063
+ dateEnd,
13064
+ offset,
13065
+ orderBy
13066
+ }) => {
13027
13067
  const isHealthy = await checkWorkerAndToast();
13028
13068
  if (!isHealthy) {
13029
13069
  return "Claude-Mem worker is offline. Start Claude-Mem and retry the search.";
13030
13070
  }
13031
- const result = await WorkerClient.search(query, projectName, limit);
13032
- return result || `No Claude-Mem results found for "${query}".`;
13071
+ const result = await WorkerClient.search({
13072
+ query,
13073
+ limit,
13074
+ project: projectFilter ?? projectName,
13075
+ platformSource,
13076
+ type,
13077
+ obs_type,
13078
+ dateStart,
13079
+ dateEnd,
13080
+ offset,
13081
+ orderBy
13082
+ });
13083
+ return result || `No Claude-Mem results found${query ? ` for "${query}"` : ""}.`;
13033
13084
  }
13034
13085
  }),
13035
13086
  "mem-timeline": tool({
package/dist/tui.js CHANGED
@@ -48,6 +48,27 @@ function parseWorkerEndpoint(env, rawSettings) {
48
48
  const port = readPort(env["CLAUDE_MEM_WORKER_PORT"]) ?? readPort(settings["CLAUDE_MEM_WORKER_PORT"]) ?? DEFAULT_WORKER_PORT;
49
49
  return { host, port };
50
50
  }
51
+ function buildSearchParams(options) {
52
+ const params = new URLSearchParams;
53
+ const entries = [
54
+ ["query", options.query ?? ""],
55
+ ["limit", options.limit],
56
+ ["project", options.project],
57
+ ["platformSource", options.platformSource],
58
+ ["type", options.type],
59
+ ["obs_type", options.obs_type],
60
+ ["dateStart", options.dateStart],
61
+ ["dateEnd", options.dateEnd],
62
+ ["offset", options.offset],
63
+ ["orderBy", options.orderBy]
64
+ ];
65
+ for (const [key, value] of entries) {
66
+ if (value !== undefined) {
67
+ params.set(key, String(value));
68
+ }
69
+ }
70
+ return params;
71
+ }
51
72
  function readSettingsRaw() {
52
73
  try {
53
74
  return readFileSync(SETTINGS_FILE, "utf8");
@@ -359,12 +380,9 @@ class WorkerClient {
359
380
  return [];
360
381
  }
361
382
  }
362
- static async search(query, project, limit) {
383
+ static async search(options) {
363
384
  try {
364
- const params = new URLSearchParams({ query, project });
365
- if (limit !== undefined) {
366
- params.set("limit", String(limit));
367
- }
385
+ const params = buildSearchParams(options);
368
386
  const response = await fetch(`${this.BASE_URL}/api/search?${params.toString()}`);
369
387
  if (!response.ok) {
370
388
  return "";
@@ -3,7 +3,21 @@ export interface WorkerEndpoint {
3
3
  readonly port: number;
4
4
  }
5
5
  export type AutoStartResult = 'already-running' | 'started' | 'no-bun' | 'spawn-failed' | 'timeout' | 'skipped';
6
+ export type SearchOrder = 'date_desc' | 'date_asc' | 'relevance';
7
+ export interface SearchOptions {
8
+ readonly query?: string;
9
+ readonly limit?: number;
10
+ readonly project?: string;
11
+ readonly platformSource?: string;
12
+ readonly type?: string;
13
+ readonly obs_type?: string;
14
+ readonly dateStart?: string;
15
+ readonly dateEnd?: string;
16
+ readonly offset?: number;
17
+ readonly orderBy?: SearchOrder;
18
+ }
6
19
  export declare function parseWorkerEndpoint(env: Record<string, string | undefined>, rawSettings: string | null): WorkerEndpoint;
20
+ export declare function buildSearchParams(options: SearchOptions): URLSearchParams;
7
21
  export declare class WorkerClient {
8
22
  private static readonly BASE_URL;
9
23
  /** Per-process guard so multiple OpenCode windows don't all spawn workers. */
@@ -110,5 +124,5 @@ export declare class WorkerClient {
110
124
  type: string;
111
125
  title: string;
112
126
  }[]>;
113
- static search(query: string, project: string, limit?: number): Promise<string>;
127
+ static search(options: SearchOptions): Promise<string>;
114
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ephemushroom/opencode-claude-mem",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "OpenCode plugin for Claude-Mem persistent memory system",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -53,10 +53,10 @@
53
53
  "access": "public"
54
54
  },
55
55
  "devDependencies": {
56
- "@opencode-ai/plugin": "^1.3.0",
56
+ "@opencode-ai/plugin": "^1.14.41",
57
57
  "@types/bun": "latest",
58
58
  "oxfmt": "^0.33.0",
59
- "oxlint": "^1.48.0",
60
- "typescript": "^5.0.0"
59
+ "oxlint": "^1.63.0",
60
+ "typescript": "^5.9.3"
61
61
  }
62
62
  }