@ephemushroom/opencode-claude-mem 0.4.4 → 0.6.0

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,6 +3,12 @@
3
3
  Persistent memory for [OpenCode](https://opencode.ai), powered by
4
4
  [Claude-Mem](https://github.com/thedotmack/claude-mem).
5
5
 
6
+ **Supports OpenCode 1 and OpenCode 2 with the same package name from v0.5.0.**
7
+ No `/v2` or `/cli` suffix is required. Existing version-specific entries remain compatible.
8
+
9
+ **v0.6.0** adds per-turn memory tracking, `mem-save`, file history and opt-in semantic
10
+ injection, and fixes the OpenCode 2 sidebar's `No renderer found` crash.
11
+
6
12
  Share the same Claude-Mem worker, database, and memory across your coding
7
13
  agents: memories written by Claude Code are visible to OpenCode, and vice
8
14
  versa. Previous observations and summaries are injected into new OpenCode
@@ -24,7 +30,10 @@ flowchart LR
24
30
  ## Quick Start
25
31
 
26
32
  1. Install and configure Claude-Mem in Claude Code.
27
- 2. Add this plugin to your `opencode.json`:
33
+ 2. Add this plugin to your `opencode.json` — pick the section matching your
34
+ OpenCode version:
35
+
36
+ ### OpenCode 1
28
37
 
29
38
  ```json
30
39
  {
@@ -32,21 +41,78 @@ flowchart LR
32
41
  }
33
42
  ```
34
43
 
44
+ ### OpenCode 2
45
+
46
+ ```json
47
+ {
48
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
49
+ }
50
+ ```
51
+
35
52
  3. Restart OpenCode.
36
53
  4. Start a session — memory context is injected automatically, the `mem-*`
37
54
  tools become available, and a collapsible **Memory** section appears in the
38
- sidebar.
55
+ sidebar (both versions).
39
56
 
40
57
  Using [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent)?
41
58
  See [Disable the Claude Code bridge](#using-with-oh-my-openagent-disable-the-claude-code-bridge)
42
59
  to avoid running two claude-mem integrations at once.
43
60
 
61
+ ## OpenCode 1 and 2
62
+
63
+ OpenCode 1 and OpenCode 2 install side by side (`opencode` vs `opencode2`) and
64
+ their plugin APIs are incompatible. This package exposes **one bare package
65
+ name for both runtimes**, with thin bridges that select the existing adapter
66
+ contract. No `/v2` or `/cli` suffix is needed:
67
+
68
+ | | OpenCode 1 | OpenCode 2 |
69
+ |---|---|---|
70
+ | **Binary** | `opencode` | `opencode2` |
71
+ | **Server plugin config** | `opencode.json` → `plugin` | `opencode.json` → `plugins` |
72
+ | **Server plugin entry** | `@ephemushroom/opencode-claude-mem` | `@ephemushroom/opencode-claude-mem` |
73
+ | **Sidebar config** | `~/.config/opencode/tui.json` → `plugin` (self-healed) | `~/.config/opencode/cli.json` → `plugins` (self-healed) |
74
+ | **Sidebar config entry** | `@ephemushroom/opencode-claude-mem` | `@ephemushroom/opencode-claude-mem` |
75
+ | **Sidebar resolution** | package `./tui` export, `tui()` | package `./tui` export, `setup()` |
76
+
77
+ Both adapters talk to the same Claude-Mem worker, so memory written from V1,
78
+ V2, and Claude Code is shared. The V2 entrypoint covers the same behavior —
79
+ context injection, tool observation capture, the `mem-search`/`mem-timeline`/
80
+ `mem-get-observations`/`mem-save` tools, per-user-message registration and session summarize/cleanup via the event
81
+ stream.
82
+
83
+ The bare package is tested with `opencode 1.18.29` and
84
+ `opencode2 0.0.0-beta-19151`; v0.6.0 also passes server and real
85
+ sidebar interaction checks on `0.0.0-beta-19192`. The V2 SDK is pinned because preview APIs can
86
+ change. Unified routing requires **v0.5.0 or later**; the older `0.4.4` package
87
+ does not provide it. Upgrade the package in both server and sidebar configurations.
88
+ Existing `/server`, `/tui`, `/v2`, and `/cli` exports remain supported; replace
89
+ an old config entry rather than adding the bare name beside it.
90
+
91
+ After upgrading, finish active tasks, close the client, and restart the V2
92
+ background service with `opencode2 service restart` before reopening `opencode2`.
93
+ For V1, restart `opencode`. Verify server activation with `opencode2 plugin list`.
94
+
95
+ In V2, installing the server plugin
96
+ (`"plugins": ["@ephemushroom/opencode-claude-mem"]`) auto-appends the same
97
+ bare package to `~/.config/opencode/cli.json` on first load. Restart OpenCode
98
+ after that first load if the sidebar is not yet present. Beta `19151` still
99
+ uses a separate CLI config; this is not a promise of same-launch discovery
100
+ from the server config alone. To register manually, use:
101
+
102
+ ```json
103
+ {
104
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
105
+ }
106
+ ```
107
+
44
108
  ## What You Get
45
109
 
46
110
  | Surface | What it looks like |
47
111
  |---|---|
48
112
  | **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) |
113
+ | **Tools** | `mem-search` → `mem-timeline` → `mem-get-observations`, plus explicit `mem-save` (no MCP server needed) |
114
+ | **Read history** | Bounded supplementary file history after eligible file reads; original file output is preserved |
115
+ | **Optional semantic context** | Relevant project memories for the current real user prompt; **off by default** |
50
116
  | **Sidebar** | `▶ Memory (online, 11.7k obs)` — click to expand recent sessions and latest observations |
51
117
  | **Background** | Every tool call, assistant message, and file edit captured as observations; sessions summarized on idle |
52
118
 
@@ -79,15 +145,21 @@ MCP/Context sections — borderless, click-to-toggle:
79
145
  loop cheap (stats every 5s).
80
146
  - Fails open: worker offline → shows the offline state, never blocks the TUI.
81
147
 
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.
148
+ The sidebar loads via the package's `./tui` bridge in both versions (OpenCode 2
149
+ registers the bare name in `~/.config/opencode/cli.json`). On startup
150
+ the plugin **self-heals the TUI config**: if it is registered as a server
151
+ plugin but missing from the TUI plugin list, it appends itself — no manual
152
+ configuration. OpenCode 1 writes into `tui.json` (symlinked files are written
153
+ through, preserving dotfiles setups); OpenCode 2 appends
154
+ `@ephemushroom/opencode-claude-mem` to `cli.json`'s `plugins` array.
155
+ Existing bare, `/cli`, or `/tui` sidebar entries (including versions and
156
+ object-form options) are left untouched, without adding another entry.
157
+ Malformed or comment-bearing CLI config is left untouched; add the bare name
158
+ manually in that case.
87
159
 
88
160
  ### Memory Tools
89
161
 
90
- Three native OpenCode tools cover the same 3-step workflow as the upstream
162
+ Three native OpenCode tools cover the same 3-step search workflow as the upstream
91
163
  Claude-Mem MCP server — no MCP server or stdio subprocess required:
92
164
 
93
165
  ```mermaid
@@ -101,6 +173,118 @@ flowchart LR
101
173
  | `mem-search` | `GET /api/search` | Formatted index with query, project, source, type, date, pagination, and ordering filters |
102
174
  | `mem-timeline` | `GET /api/timeline` | Chronological records around an `anchor` ID (or auto-located via `query`) |
103
175
  | `mem-get-observations` | `POST /api/observations/batch` | Full details for IDs — e.g. the IDs shown in the injected context |
176
+ | `mem-save` | `POST /api/memory/save` | Explicitly save required `text`, optional `title` and `project` (defaults to current project) |
177
+
178
+ `mem-save` strips `<private>` and `<claude-mem-context>` material from text/title,
179
+ rejects empty/fully stripped content and values above 24 KiB, and reports success
180
+ only for a valid Worker acknowledgement. It never retries automatically: after
181
+ a timeout, check with search before retrying because the write may have succeeded.
182
+ Manual saves do not require an observation-model inference pass, but the Worker
183
+ still performs asynchronous vector indexing; this is **not** a promise of zero
184
+ embedding cost. Native and namespaced `mem-save` calls are excluded from automatic
185
+ observation capture.
186
+
187
+ ### Memory configuration (both runtimes)
188
+
189
+ The per-turn registration, `mem-save`, file history, and semantic options described
190
+ here require **v0.6.0 or later**. Upgrade both server and sidebar package entries;
191
+ `0.5.0` does not include these features or the renderer fix.
192
+
193
+ Configuration is read once when the server plugin is set up. Quit and restart
194
+ OpenCode after changing options, environment, or settings; on V2 also restart the
195
+ background service. No semantic feature is enabled globally by this plugin.
196
+
197
+ **OpenCode 1** uses a tuple in `opencode.json`:
198
+
199
+ ```json
200
+ {
201
+ "plugin": [["@ephemushroom/opencode-claude-mem", {
202
+ "semanticInjection": { "enabled": true, "limit": 5, "maxChars": 6000, "timeoutMs": 2000 },
203
+ "fileContext": { "enabled": true, "limit": 15, "maxChars": 6000, "timeoutMs": 1500 }
204
+ }]]
205
+ }
206
+ ```
207
+
208
+ **OpenCode 2** uses a package/options object (not a V1 tuple):
209
+
210
+ ```json
211
+ {
212
+ "plugins": [{
213
+ "package": "@ephemushroom/opencode-claude-mem",
214
+ "options": {
215
+ "semanticInjection": { "enabled": true, "limit": 5, "maxChars": 6000, "timeoutMs": 2000 },
216
+ "fileContext": { "enabled": true, "limit": 15, "maxChars": 6000, "timeoutMs": 1500 }
217
+ }
218
+ }]
219
+ }
220
+ ```
221
+
222
+ | Option | Semantic default | File-history default | Accepted values |
223
+ |---|---|---|---|
224
+ | `enabled` | `false` | `true` | Boolean |
225
+ | `limit` | `5` | `15` | Integer 1–50 |
226
+ | `maxChars` | `6000` | `6000` | Integer 256–24000; supplementary payload only |
227
+ | `timeoutMs` | `2000` | `1500` | Integer 100–10000 |
228
+
229
+ Per-field precedence for semantic `enabled` and `limit` is **explicit plugin
230
+ options > environment > `~/.claude-mem/settings.json` > defaults**. The environment
231
+ and settings keys are `CLAUDE_MEM_SEMANTIC_INJECT` (`true`/`false` or `1`/`0`) and
232
+ `CLAUDE_MEM_SEMANTIC_INJECT_LIMIT` (integer 1–50). Settings may use booleans/numbers
233
+ or their string equivalents. Explicit `false` overrides environment `true`.
234
+ Invalid values fall through to the next valid source; other controls use explicit
235
+ options then defaults. Settings are read-only and never logged.
236
+
237
+ Semantic retrieval uses `POST /api/context/semantic` with `{q, project, limit}`
238
+ and reads JSON `{context, count}`. `q` is the sanitized real current prompt,
239
+ at least 20 characters; media-only, fully private, and identifiable internal
240
+ messages are excluded. No `platformSource` filter is sent, so Claude Code and
241
+ OpenCode memories remain shared. Each real prompt identity gets at most one
242
+ retrieval attempt, including failure/timeout, reused across tool-loop dispatches.
243
+ The system block is replaced rather than accumulated. Old Workers, malformed
244
+ responses and offline retrieval fail open.
245
+
246
+ File history uses `GET /api/observations/by-file` with repeated `path` parameters
247
+ (absolute and project-relative, normalized to forward slashes), `projects` and `limit`. Supported read arguments
248
+ include V1 `filePath`, V2 `path`, and `file_path`/`filePaths`/`file_paths` aliases.
249
+ Only existing regular files of at least 1500 bytes qualify, at most 10 paths per
250
+ read. Lookups run concurrently within one configured per-read deadline (including
251
+ file metadata lookup), not ten serial timeout periods. Rows are deduplicated by
252
+ memory session and the displayed text is bounded; a file whose mtime is at least
253
+ the newest observation timestamp gets no history. Delivered history is deduplicated per
254
+ session/file revision (mtime and size); failures and timeouts may retry on a later read.
255
+ Editing the file or starting a new session also permits another lookup. Supplementary history is explicitly
256
+ labeled and wrapped in memory tags, appended without replacing/truncating the
257
+ original read result, and never included in the automatic tool observation.
258
+ Known child sessions (`parentID` when supplied by the host) skip file-history
259
+ enrichment; V1 resumed sessions without that metadata cannot reliably be identified
260
+ as subagents and are not guessed from the agent's name.
261
+
262
+ ### Prompt lifecycle and Worker limitations
263
+
264
+ V1 registers stable `chat.message` IDs; V2 records admission IDs without writing,
265
+ then registers only IDs present in delivered `session.context` records at model
266
+ dispatch. Thus queued V2 prompts do not advance the Worker's current prompt.
267
+ Resumed sessions without local admission history use the latest delivered real
268
+ user turn, not a synthetic `SESSION_START`, and tool callbacks do not reread the
269
+ whole history. Registrations are serialized per session and successful IDs are
270
+ deduplicated locally; a failed registration retains its real prompt for retry.
271
+ Media-only turns use `[media prompt]`. Fully private or identifiable synthetic
272
+ turns suppress automatic observations, summaries and semantic retrieval locally,
273
+ including when the Worker reports a skipped/private registration.
274
+
275
+ **Upstream v13.24.1 still deduplicates identical prompt text within its own time
276
+ window and has no consumed external prompt-ID contract.** This plugin submits
277
+ distinct IDs even if their text is identical, without prefixing/changing the text,
278
+ but cannot guarantee distinct database prompt rows. A `duplicate` acknowledgement
279
+ is accepted; other skipped registrations do not authorize automatic capture.
280
+ V1 exposes submission callbacks rather than V2's delivered/inbox distinction;
281
+ it cannot provide the same queued-consumption guarantee. A restart cannot restore
282
+ the plugin's in-memory ID dedupe ledger, so upstream dedupe remains relevant.
283
+
284
+ Base context is session-isolated, limited to 24000 characters, with a two-second
285
+ request timeout. It is refreshed at V1 compaction start/end and V2 compaction
286
+ requests/completed events or changed compaction records. Failed reads are retryable;
287
+ an invalidated or deleted session cannot be repopulated by an older in-flight read.
104
288
 
105
289
  ## How It Works
106
290
 
@@ -111,27 +295,46 @@ sequenceDiagram
111
295
  participant W as Claude-Mem Worker
112
296
 
113
297
  OC->>P: plugin loads
114
- P->>P: self-heal tui.json
298
+ P->>P: self-heal tui.json / cli.json
115
299
  P->>W: health check (auto-start via bunx if down)
116
- OC->>P: chat.message (first user prompt)
300
+ OC->>P: each real user prompt (chat.message / delivered context)
117
301
  P->>W: POST /api/sessions/init
118
- OC->>P: system.transform
302
+ OC->>P: system prompt transform / context hook
119
303
  W-->>P: GET /api/context/inject (cached per session)
120
304
  P-->>OC: inject <claude-mem-context> into system prompt
121
305
  loop during the session
122
- OC->>P: tool.execute.after / message.updated / file.edited
306
+ OC->>P: tool.execute.after / assistant text / file.edited
123
307
  P->>W: POST /api/sessions/observations
124
308
  end
125
- OC->>P: session.idle / session.compacted
309
+ OC->>P: session idle / execution succeeded / compaction ended
126
310
  P->>W: POST /api/sessions/summarize
127
311
  OC->>P: session.deleted
128
- P->>W: POST /api/sessions/complete
312
+ P->>P: flush pending observations and release local state
129
313
  ```
130
314
 
131
315
  The plugin is intentionally small: it only adapts OpenCode hook events to the
132
316
  Claude-Mem worker HTTP API. All indexing, summarization, memory search, and
133
317
  storage stay in upstream Claude-Mem.
134
318
 
319
+ ### Hook Mapping (V1 ↔ V2)
320
+
321
+ The V1 and V2 entrypoints adapt the same behavior onto two different plugin
322
+ APIs:
323
+
324
+ | Behavior | OpenCode 1 | OpenCode 2 |
325
+ |---|---|---|
326
+ | Inject memory context | `experimental.chat.system.transform` | `ctx.session.hook('context')` |
327
+ | Refresh memory on compaction | `experimental.session.compacting` + `session.compacted` | compaction model requests/events + delivered compaction records |
328
+ | Register each real user prompt | `chat.message` (message ID) | `ctx.session.hook('context')` (`ctx.session.context` delivered IDs; not inbox admission) |
329
+ | Capture tool observations | `tool.execute.after` | `ctx.tool.hook('execute.after')` |
330
+ | Custom memory tools | `tool` (`mem-search`, …) | `ctx.tool.transform` (`tools.add`) |
331
+ | Assistant text capture | `event` (`message.updated`, debounced 250ms) | `session.text.ended` event (complete text) |
332
+ | File edit observations | `event` (`file.edited`) | `ctx.tool.hook('execute.after')` |
333
+ | Summarize on idle | `event` (`session.idle`) | `session.execution.succeeded` event |
334
+ | Summarize after compaction | `event` (`session.compacted`) | `session.compaction.ended` event |
335
+ | Release session tracking | `event` (`session.deleted`) | `session.deleted` event |
336
+ | Sidebar slot | `api.slots.register` (`sidebar_content`) | `ctx.ui.slot({ append: 'sidebar.content', render })` |
337
+
135
338
  ### Cross-Tool Memory Sharing
136
339
 
137
340
  Writes are attributed (`platformSource: "opencode"`), reads are shared:
@@ -169,12 +372,25 @@ Restart Claude Code so the worker can start and initialize its data directory.
169
372
 
170
373
  Add this plugin to your project or global `opencode.json`:
171
374
 
375
+ **OpenCode 1** (`opencode`):
376
+
172
377
  ```json
173
378
  {
174
379
  "plugin": ["@ephemushroom/opencode-claude-mem"]
175
380
  }
176
381
  ```
177
382
 
383
+ **OpenCode 2** (`opencode2`):
384
+
385
+ ```json
386
+ {
387
+ "plugins": ["@ephemushroom/opencode-claude-mem"]
388
+ }
389
+ ```
390
+
391
+ The two runtimes can be installed side by side and use the same package name.
392
+ Their config keys remain different: `plugin` in V1, `plugins` in V2.
393
+
178
394
  Then restart OpenCode.
179
395
 
180
396
  ### Step 3: Verify
@@ -183,9 +399,9 @@ Then restart OpenCode.
183
399
  curl -s http://127.0.0.1:37777/api/health
184
400
  ```
185
401
 
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)`.
402
+ If the worker is healthy, the sidebar shows `▶ Memory (online, … obs)`.
403
+ OpenCode 1 also shows a `Memory active · <project>` toast when a session
404
+ starts (OpenCode 2 only toasts a warning when the worker is offline).
189
405
 
190
406
  ## Using with oh-my-openagent: Disable the Claude Code Bridge
191
407
 
@@ -227,20 +443,23 @@ worker. Everything the bridge provided is covered natively by this plugin:
227
443
 
228
444
  ### Hook Mapping
229
445
 
230
- | Claude Code | OpenCode plugin | Purpose |
446
+ Claude Code hooks → this plugin's adapters (see [Hook Mapping (V1 ↔ V2)](#hook-mapping-v1--v2)
447
+ for the per-runtime API):
448
+
449
+ | Claude Code | OpenCode (both runtimes) | Purpose |
231
450
  |---|---|---|
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) |
451
+ | `SessionStart` | system prompt injection (`system.transform` / `context` hook) | Inject memory context |
452
+ | `SessionStart` | compaction injection (`session.compacting` / context hook) | Preserve memory during compaction |
453
+ | `UserPromptSubmit` | session init (`chat.message` / context hook) | Initialize session with real user prompt |
454
+ | `PostToolUse` | observation capture (`tool.execute.after` / `execute.after` hook) | Capture tool observations |
455
+ | Claude-Mem MCP `search` | `mem-search` tool | Search memory from OpenCode |
456
+ | Claude-Mem MCP `timeline` | `mem-timeline` tool | Chronological context around an observation |
457
+ | Claude-Mem MCP `get_observations` | `mem-get-observations` tool | Fetch full observation details by ID |
458
+ | _(streaming)_ | assistant text (`message.updated` / `session.text.ended`) | Capture assistant text |
459
+ | _(streaming)_ | file edits (`file.edited` / tool hook) | Record file edit observations |
460
+ | _(compaction)_ | `session.compacted` / `session.compaction.ended` | Summarize after OpenCode compacts |
461
+ | `Stop` | `session.idle` / `session.execution.succeeded` | Flush + summarize |
462
+ | `SessionEnd` | `session.deleted` | Flush observations and release local state; Worker self-completes |
244
463
 
245
464
  ### Worker API Endpoints Used
246
465
 
@@ -251,10 +470,12 @@ worker. Everything the bridge provided is covered natively by this plugin:
251
470
  | `POST` | `/api/sessions/init` | Initialize session |
252
471
  | `POST` | `/api/sessions/observations` | Store tool observation |
253
472
  | `POST` | `/api/sessions/summarize` | Trigger summarization |
254
- | `POST` | `/api/sessions/complete` | Complete session |
255
473
  | `GET` | `/api/search?query=...&project=...&dateStart=...&dateEnd=...` | `mem-search`; also supports `limit`, `platformSource`, `type`, `obs_type`, `offset`, and `orderBy` |
256
474
  | `GET` | `/api/timeline?project={name}&anchor={id}` | `mem-timeline` |
257
475
  | `POST` | `/api/observations/batch` | `mem-get-observations` |
476
+ | `POST` | `/api/memory/save` | Explicit `mem-save`, nested `metadata.platformSource` attribution |
477
+ | `POST` | `/api/context/semantic` | Opt-in semantic context; JSON `{context,count}` response |
478
+ | `GET` | `/api/observations/by-file?path=...&path=...&projects=...&limit=...` | Supplementary file history |
258
479
  | `GET` | `/api/stats` + `/api/processing-status` | Sidebar status |
259
480
  | `GET` | `/api/summaries` + `/api/observations` | Sidebar recent items (expanded only) |
260
481
 
@@ -264,11 +485,26 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
264
485
 
265
486
  ### Key Implementation Details
266
487
 
267
- - **Thin client architecture** — `src/index.ts` handles OpenCode hooks and
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.
488
+ - **Dual-runtime architecture** — `src/index.ts` is the OpenCode 1 plugin,
489
+ `src/v2.ts` the OpenCode 2 plugin (`Plugin.define`), `src/tui.ts` the V1
490
+ sidebar, `src/cli.ts` the V2 sidebar; `src/shared.ts` and
491
+ `src/sidebar-model.ts` hold the shared pure helpers; `src/worker-client.ts`
492
+ is a static HTTP client; `src/tui-registration.ts` self-heals `tui.json`
493
+ (V1) and `cli.json` (V2).
494
+ - **Static package bridges** — `src/package-server.ts` exposes `{ id, server,
495
+ setup }` at both `.` and `./server`; `src/package-tui.ts` exposes `{ id, tui,
496
+ setup }` at `./tui`. V1 reads `server`/`tui`; V2 reads `setup`. Neither bridge
497
+ invokes both adapters. Both installed server loaders prefer `./server`, so
498
+ routing only the root export to V2 is insufficient. Never combine `server`
499
+ and `tui` in one object: V1 rejects that shape.
500
+ - **Host renderer ownership** — the built TUI bridge uses explicit `.js`
501
+ relative imports so the host discovers both adapter modules and routes their
502
+ OpenTUI/Solid imports to its own runtime. Extensionless bridge imports can bypass
503
+ that prescan and crash the sidebar with `No renderer found`.
504
+ - **Zero runtime dependencies for server plugins** — the OpenCode plugin SDK
505
+ is bundled into `dist/` (V1 `index.js` ~490 KB, V2 `v2.js` ~160 KB including
506
+ bounded-response validation, using a deep SDK import that skips effect). The TUI entrypoints keep
507
+ `@opentui/solid` external (provided by the host TUI).
272
508
  - **Reactive sidebar** — collapse state and view data are solid-js signals
273
509
  (shared with OpenCode's own solid instance via `--external solid-js`), so
274
510
  clicking the header re-renders reliably; falls back to plain closures if
@@ -279,9 +515,10 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
279
515
  avoiding startup crashes caused by early TUI access.
280
516
  - **Auto-start** — if the worker is down on load, spawns
281
517
  `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
284
- across prompt injection and compaction.
518
+ on `PATH`). Health is rechecked on subsequent hooks, so a recovered Worker
519
+ does not require restarting OpenCode.
520
+ - **Context caching** — base memory context is reused per session until a
521
+ compaction boundary invalidates it. Failed fetches are not cached.
285
522
  - **Circular memory protection** — injected context is wrapped in
286
523
  `<claude-mem-context>` tags, Claude-Mem search tools are skipped from
287
524
  observation capture, and memory tags are stripped before storage.
@@ -289,9 +526,13 @@ The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
289
526
  truncated by UTF-8 byte size (24 KB cap).
290
527
  - **Field name correctness** — worker payloads use `contentSessionId`, not
291
528
  `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`.
529
+ - **Session lifecycle hygiene** — `session.deleted` flushes pending observations
530
+ and releases local tracking. Claude-Mem self-completes processing; the removed
531
+ `/api/sessions/complete` endpoint is not called.
532
+ - **Attribution** — observations include the OpenCode call ID as `tool_use_id`;
533
+ summaries include the latest assistant's `observedModel` when available.
534
+ - **Write status** — failed HTTP writes return failure without throwing; a summary
535
+ toast is shown only after the Worker accepts the request, not after generation.
295
536
 
296
537
  ## Troubleshooting
297
538
 
@@ -320,9 +561,13 @@ plugin load. If the toast still appears:
320
561
 
321
562
  ### Sidebar Memory section is missing
322
563
 
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.
564
+ - **OpenCode 1**: check `~/.config/opencode/tui.json` contains this plugin in
565
+ its `plugin` array — the plugin self-heals this file on load, so restarting
566
+ OpenCode twice (once to heal, once to load) fixes a missing entry.
567
+ - **OpenCode 2**: check `~/.config/opencode/cli.json` contains
568
+ `@ephemushroom/opencode-claude-mem` in its `plugins` array — self-healed
569
+ the same way (comment-bearing cli.json files are left untouched, so remove
570
+ the comments or add the entry manually).
326
571
  - The sidebar requires OpenCode's `@opentui/solid` runtime; if unavailable the
327
572
  section is skipped silently while hooks and tools keep working.
328
573
 
@@ -346,15 +591,35 @@ plugin load. If the toast still appears:
346
591
 
347
592
  ```bash
348
593
  bun install
349
- bun run build # bundle dist/index.js + dist/tui.js + emit declarations
350
- bun test # bun's built-in runner
594
+ bun run build # four adapters + package-server/package-tui bridges + declarations
595
+ bun test # build first: export tests import the actual dist artifacts
351
596
  bun run lint # oxlint
352
597
  bun run fmt:check # oxfmt
598
+ bun src/test-fixtures/v2-runtime.ts # installed V2: packed bare package, local model + Worker
599
+ bun src/test-fixtures/v2-runtime.ts v1 # installed V1: same package and behavior assertions
600
+ bun src/test-fixtures/tui-runtime.ts # installed V2: real PTY session, render and mouse toggle
601
+ bun src/test-fixtures/tui-runtime.ts offline # offline sidebar still renders and toggles
602
+ bun --conditions=browser src/test-fixtures/cli-driver.ts render # real rendering + mouse click
353
603
  ```
354
604
 
355
605
  If you edit source code locally, rebuild and restart OpenCode to pick up the
356
606
  new plugin bundle.
357
607
 
608
+ The runtime QA drivers never write to your real config or memory database and
609
+ never use paid models. They pack the current build, serve it through a temporary
610
+ local registry, and ask the installed host to resolve the bare npm name in an
611
+ isolated home/cache. Registry access is needed for package dependencies. Server
612
+ QA drives base/opt-in semantic injection, exactly-once tool capture, all four memory tools,
613
+ private-save rejection, file-history enrichment without capture pollution, and
614
+ summarization. HTTP regression fixtures also cover repeated/distinct IDs, queued
615
+ V2 admission versus consumption, private turns, retries and compaction invalidation.
616
+ The V2 TUI probe uses ConPTY and xterm terminal cells to open a real session and
617
+ capture collapsed, expanded, and re-collapsed states with actual mouse events.
618
+ It checks online/offline operation, unchanged CLI config, and zero model requests.
619
+ Set `MEM_TUI_EVIDENCE_DIR` to retain text and truecolor ANSI captures. The separate
620
+ OpenTUI driver covers component behavior. V1's interactive host remains outside
621
+ this check; its server runtime is verified. Browser PNG fidelity is not claimed.
622
+
358
623
  ## License
359
624
 
360
625
  MIT
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OpenCode V2 TUI plugin for Claude-Mem (the Memory sidebar).
3
+ *
4
+ * Loaded from `~/.config/opencode/cli.json` via the package's `./cli`
5
+ * export:
6
+ *
7
+ * ```json
8
+ * { "plugins": ["@ephemushroom/opencode-claude-mem/cli"] }
9
+ * ```
10
+ *
11
+ * V2 TUI plugins are `Plugin.define({ id, setup })` modules rendered inside
12
+ * the TUI process with @opentui/solid elements — the same element model as the
13
+ * V1 sidebar, but registered through a `sidebar.content` slot claim with
14
+ * reactive state from `ctx.storage.memory` (survives hot reloads).
15
+ */
16
+ declare const _default: import("@opencode-ai/plugin-v2/tui/plugin").Definition;
17
+ export default _default;