@ephemushroom/opencode-claude-mem 0.3.0 → 0.3.2
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 +49 -18
- package/dist/index.js +141 -28
- package/dist/worker-client.d.ts +6 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ into new OpenCode sessions automatically.
|
|
|
9
9
|
|
|
10
10
|
> **Note:** This plugin is a thin OpenCode adapter for an existing Claude-Mem
|
|
11
11
|
> installation. It does **not** install Claude-Mem, manage slash commands, or
|
|
12
|
-
>
|
|
12
|
+
> register Claude Code MCP servers for you.
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
@@ -39,6 +39,9 @@ into new OpenCode sessions automatically.
|
|
|
39
39
|
compaction so long conversations keep their historical context.
|
|
40
40
|
- **Observation Capture** — Sends tool observations to Claude-Mem for future
|
|
41
41
|
retrieval and summarization.
|
|
42
|
+
- **Native Memory Search** — Exposes a `mem-search` OpenCode tool backed by the
|
|
43
|
+
Claude-Mem worker search API, so search remains available without the Claude
|
|
44
|
+
Code compatibility bridge.
|
|
42
45
|
- **Observation Hardening** — Skips low-value meta tools, strips
|
|
43
46
|
`<claude-mem-context>` and `<private>` tags before storage, and truncates
|
|
44
47
|
oversized observation payloads by UTF-8 byte size.
|
|
@@ -51,8 +54,9 @@ into new OpenCode sessions automatically.
|
|
|
51
54
|
OpenCode session
|
|
52
55
|
|
|
|
53
56
|
|-- plugin loads
|
|
54
|
-
| '-- connect to Claude-Mem worker
|
|
57
|
+
| '-- connect to Claude-Mem worker from env/settings/default endpoint
|
|
55
58
|
|
|
|
59
|
+
|-- tool(mem-search) ............... search memory via worker API
|
|
56
60
|
|-- session.created ................ track session + reset cache
|
|
57
61
|
|-- chat.message ................... init session with real user prompt
|
|
58
62
|
|-- tool.execute.after ............. send tool observation
|
|
@@ -61,7 +65,7 @@ OpenCode session
|
|
|
61
65
|
|-- message.updated ................ capture assistant text (debounced)
|
|
62
66
|
|-- file.edited .................... record file edit observation
|
|
63
67
|
|-- session.compacted .............. summarize after compaction
|
|
64
|
-
|-- session.idle ................... flush + summarize
|
|
68
|
+
|-- session.idle ................... flush + summarize
|
|
65
69
|
'-- session.deleted ................ flush + complete (no zombie sessions)
|
|
66
70
|
```
|
|
67
71
|
|
|
@@ -72,9 +76,13 @@ storage stay in upstream Claude-Mem.
|
|
|
72
76
|
## Architecture
|
|
73
77
|
|
|
74
78
|
```
|
|
75
|
-
OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker
|
|
79
|
+
OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker <-> SQLite + ChromaDB
|
|
76
80
|
```
|
|
77
81
|
|
|
82
|
+
The worker endpoint is resolved in this order: `CLAUDE_MEM_WORKER_HOST` /
|
|
83
|
+
`CLAUDE_MEM_WORKER_PORT` environment variables, then
|
|
84
|
+
`~/.claude-mem/settings.json`, then `127.0.0.1:37777`.
|
|
85
|
+
|
|
78
86
|
### Hook Mapping
|
|
79
87
|
|
|
80
88
|
| Claude Code | OpenCode plugin | Purpose |
|
|
@@ -83,10 +91,11 @@ OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker (port 37777) <-> SQLite +
|
|
|
83
91
|
| `SessionStart` | `experimental.session.compacting` | Preserve memory during compaction |
|
|
84
92
|
| `UserPromptSubmit` | `chat.message` | Initialize session with real user prompt |
|
|
85
93
|
| `PostToolUse` | `tool.execute.after` | Capture tool observations |
|
|
94
|
+
| Claude-Mem MCP | `tool` (`mem-search`) | Search memory from OpenCode |
|
|
86
95
|
| _(streaming)_ | `event` (`message.updated`) | Capture assistant text (debounced) |
|
|
87
96
|
| _(streaming)_ | `event` (`file.edited`) | Record file edit observations |
|
|
88
97
|
| _(compaction)_ | `event` (`session.compacted`) | Summarize after OpenCode compacts |
|
|
89
|
-
| `Stop` | `event` (`session.idle`) | Flush + summarize
|
|
98
|
+
| `Stop` | `event` (`session.idle`) | Flush + summarize |
|
|
90
99
|
| `SessionEnd` | `event` (`session.deleted`) | Flush + complete (no zombie active rows) |
|
|
91
100
|
|
|
92
101
|
### Worker API Endpoints Used
|
|
@@ -99,6 +108,11 @@ OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker (port 37777) <-> SQLite +
|
|
|
99
108
|
| `POST` | `/api/sessions/observations` | Store tool observation |
|
|
100
109
|
| `POST` | `/api/sessions/summarize` | Trigger summarization |
|
|
101
110
|
| `POST` | `/api/sessions/complete` | Complete session |
|
|
111
|
+
| `GET` | `/api/search?query={query}&project={name}` | Search memory |
|
|
112
|
+
|
|
113
|
+
Session and observation writes include `platformSource: "opencode"` so
|
|
114
|
+
Claude-Mem can attribute new OpenCode memories separately from Claude Code,
|
|
115
|
+
while context injection and `mem-search` still search the shared project memory.
|
|
102
116
|
|
|
103
117
|
## Installation
|
|
104
118
|
|
|
@@ -107,7 +121,8 @@ OpenCode <-> Plugin (this repo) <-> Claude-Mem Worker (port 37777) <-> SQLite +
|
|
|
107
121
|
- [Claude Code](https://claude.com/claude-code) with
|
|
108
122
|
[Claude-Mem](https://github.com/thedotmack/claude-mem) installed
|
|
109
123
|
- [OpenCode](https://opencode.ai) with plugin support
|
|
110
|
-
- A running Claude-Mem worker
|
|
124
|
+
- A running Claude-Mem worker (default `127.0.0.1:37777`, or your
|
|
125
|
+
`CLAUDE_MEM_WORKER_HOST` / `CLAUDE_MEM_WORKER_PORT` settings)
|
|
111
126
|
|
|
112
127
|
### Step 1: Install Claude-Mem
|
|
113
128
|
|
|
@@ -151,6 +166,8 @@ Once installed, the plugin works automatically:
|
|
|
151
166
|
OpenCode’s compaction path so memory survives conversation compression.
|
|
152
167
|
- **Tool observation capture** — Tool executions are stored as observations,
|
|
153
168
|
except for low-value/meta tools and Claude-Mem search tools.
|
|
169
|
+
- **Memory search** — The `mem-search` tool queries the worker's `/api/search`
|
|
170
|
+
endpoint for the current project.
|
|
154
171
|
- **Assistant message capture** — Assistant text from `message.updated` is
|
|
155
172
|
buffered with 250ms debounce per session and sent as a single
|
|
156
173
|
`assistant_message` observation per turn (avoids streaming-chunk floods).
|
|
@@ -166,17 +183,21 @@ Once installed, the plugin works automatically:
|
|
|
166
183
|
|
|
167
184
|
## Memory Search
|
|
168
185
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
assistant can query project memory directly with Claude-Mem’s search workflow:
|
|
186
|
+
This plugin provides a native OpenCode tool named `mem-search`. It calls the
|
|
187
|
+
Claude-Mem worker's `/api/search` endpoint for the current project and returns
|
|
188
|
+
the same formatted result text the worker exposes over HTTP.
|
|
173
189
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
Claude Code's claude-mem plugin also registers an MCP server through its own
|
|
191
|
+
`.mcp.json`. OpenCode does not automatically load Claude Code plugin MCP
|
|
192
|
+
servers. If you previously saw Claude-Mem MCP tools in OpenCode, they were most
|
|
193
|
+
likely supplied by a Claude Code compatibility bridge such as oh-my-openagent.
|
|
194
|
+
Disabling that bridge for `claude-mem@thedotmack` removes those bridged MCP
|
|
195
|
+
tools; `mem-search` is the native replacement inside this plugin.
|
|
177
196
|
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
The richer Claude-Mem MCP workflow (`search`, `timeline`, `get_observations`) can
|
|
198
|
+
still be configured separately in OpenCode if you want it. It is not required for
|
|
199
|
+
automatic context injection, observation capture, summarization, or the built-in
|
|
200
|
+
`mem-search` tool.
|
|
180
201
|
|
|
181
202
|
## Key Implementation Details
|
|
182
203
|
|
|
@@ -188,6 +209,11 @@ context back into OpenCode sessions.
|
|
|
188
209
|
avoiding startup crashes caused by early TUI access.
|
|
189
210
|
- **Real prompt initialization** — `chat.message` sends the actual user prompt
|
|
190
211
|
to Claude-Mem instead of a synthetic placeholder.
|
|
212
|
+
- **OpenCode attribution** — Session, observation, summarize, and complete
|
|
213
|
+
payloads include `platformSource: "opencode"`.
|
|
214
|
+
- **Worker endpoint resolution** — The plugin reads `CLAUDE_MEM_WORKER_HOST` /
|
|
215
|
+
`CLAUDE_MEM_WORKER_PORT`, then `~/.claude-mem/settings.json`, then defaults to
|
|
216
|
+
`127.0.0.1:37777`.
|
|
191
217
|
- **Real summarize payloads** — On `session.idle`, the plugin reads the latest
|
|
192
218
|
session messages and sends the last user and assistant messages for summary.
|
|
193
219
|
- **Context caching** — Memory context is fetched once per session and reused
|
|
@@ -199,6 +225,10 @@ context back into OpenCode sessions.
|
|
|
199
225
|
reduce token waste and avoid oversize worker payloads.
|
|
200
226
|
- **Field name correctness** — Worker payloads use `contentSessionId`, not
|
|
201
227
|
`claudeSessionId`.
|
|
228
|
+
- **PreToolUse file context** — Claude Code's native `PreToolUse Read`
|
|
229
|
+
`file-context` hook is not mirrored because the current OpenCode plugin
|
|
230
|
+
`tool.execute.before` API only lets plugins alter tool args; it has no channel
|
|
231
|
+
to inject extra LLM context before the tool call.
|
|
202
232
|
|
|
203
233
|
## Differences from `bloodf/opencode-mem`
|
|
204
234
|
|
|
@@ -253,9 +283,10 @@ plugin load. If that toast still appears:
|
|
|
253
283
|
|
|
254
284
|
### Memory search is unavailable
|
|
255
285
|
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
|
|
286
|
+
- Use the built-in `mem-search` tool exposed by this plugin.
|
|
287
|
+
- If you specifically need Claude-Mem's MCP `timeline` or `get_observations`
|
|
288
|
+
tools, configure the Claude-Mem MCP server separately in OpenCode or enable a
|
|
289
|
+
compatible bridge for MCP only.
|
|
259
290
|
|
|
260
291
|
## Development
|
|
261
292
|
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,92 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/index.ts
|
|
3
|
+
import { tool } from "@opencode-ai/plugin";
|
|
4
|
+
|
|
2
5
|
// src/worker-client.ts
|
|
3
|
-
import { existsSync } from "fs";
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
4
9
|
import { spawn } from "child_process";
|
|
5
10
|
var HEALTH_TIMEOUT_MS = 1000;
|
|
6
11
|
var AUTOSTART_POLL_INTERVAL_MS = 500;
|
|
7
12
|
var AUTOSTART_POLL_MAX_ATTEMPTS = 16;
|
|
13
|
+
var DEFAULT_WORKER_HOST = "127.0.0.1";
|
|
14
|
+
var DEFAULT_WORKER_PORT = 37777;
|
|
15
|
+
var PLATFORM_SOURCE = "opencode";
|
|
16
|
+
var SETTINGS_FILE = join(homedir(), ".claude-mem", "settings.json");
|
|
17
|
+
function isRecord(value) {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function readString(value) {
|
|
21
|
+
if (typeof value !== "string") {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
return trimmed || null;
|
|
26
|
+
}
|
|
27
|
+
function readPort(value) {
|
|
28
|
+
const text = readString(value);
|
|
29
|
+
if (!text || !/^\d+$/.test(text)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const port = Number(text);
|
|
33
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
34
|
+
}
|
|
35
|
+
function parseSettings(rawSettings) {
|
|
36
|
+
if (!rawSettings) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(rawSettings);
|
|
41
|
+
return isRecord(parsed) ? parsed : {};
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function parseWorkerEndpoint(env, rawSettings) {
|
|
47
|
+
const settings = parseSettings(rawSettings);
|
|
48
|
+
const host = readString(env["CLAUDE_MEM_WORKER_HOST"]) ?? readString(settings["CLAUDE_MEM_WORKER_HOST"]) ?? DEFAULT_WORKER_HOST;
|
|
49
|
+
const port = readPort(env["CLAUDE_MEM_WORKER_PORT"]) ?? readPort(settings["CLAUDE_MEM_WORKER_PORT"]) ?? DEFAULT_WORKER_PORT;
|
|
50
|
+
return { host, port };
|
|
51
|
+
}
|
|
52
|
+
function readSettingsRaw() {
|
|
53
|
+
try {
|
|
54
|
+
return readFileSync(SETTINGS_FILE, "utf8");
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function getWorkerBaseUrl() {
|
|
60
|
+
const endpoint = parseWorkerEndpoint(process.env, readSettingsRaw());
|
|
61
|
+
return `http://${endpoint.host}:${endpoint.port}`;
|
|
62
|
+
}
|
|
63
|
+
function extractTextContent(value) {
|
|
64
|
+
if (typeof value === "string") {
|
|
65
|
+
return value.trim() || null;
|
|
66
|
+
}
|
|
67
|
+
if (!isRecord(value)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const { content } = value;
|
|
71
|
+
if (typeof content === "string") {
|
|
72
|
+
return content.trim() || null;
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(content)) {
|
|
75
|
+
const text2 = content.map((part) => {
|
|
76
|
+
if (!isRecord(part) || part["type"] !== "text" || typeof part["text"] !== "string") {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
return part["text"];
|
|
80
|
+
}).filter(Boolean).join(`
|
|
81
|
+
`).trim();
|
|
82
|
+
return text2 || null;
|
|
83
|
+
}
|
|
84
|
+
const text = JSON.stringify(value, null, 2);
|
|
85
|
+
return text === "{}" || text === "null" ? null : text;
|
|
86
|
+
}
|
|
8
87
|
|
|
9
88
|
class WorkerClient {
|
|
10
|
-
static
|
|
11
|
-
static BASE_URL = `http://127.0.0.1:${WorkerClient.PORT}`;
|
|
89
|
+
static BASE_URL = getWorkerBaseUrl();
|
|
12
90
|
static autoStartPromise = null;
|
|
13
91
|
static async isHealthy() {
|
|
14
92
|
try {
|
|
@@ -94,7 +172,12 @@ class WorkerClient {
|
|
|
94
172
|
const response = await fetch(`${this.BASE_URL}/api/sessions/init`, {
|
|
95
173
|
method: "POST",
|
|
96
174
|
headers: { "Content-Type": "application/json" },
|
|
97
|
-
body: JSON.stringify({
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
contentSessionId,
|
|
177
|
+
project,
|
|
178
|
+
prompt,
|
|
179
|
+
platformSource: PLATFORM_SOURCE
|
|
180
|
+
})
|
|
98
181
|
});
|
|
99
182
|
if (!response.ok) {
|
|
100
183
|
return null;
|
|
@@ -114,7 +197,8 @@ class WorkerClient {
|
|
|
114
197
|
tool_name: toolName,
|
|
115
198
|
tool_input: toolInput,
|
|
116
199
|
tool_response: toolResponse,
|
|
117
|
-
cwd
|
|
200
|
+
cwd,
|
|
201
|
+
platformSource: PLATFORM_SOURCE
|
|
118
202
|
})
|
|
119
203
|
});
|
|
120
204
|
} catch {}
|
|
@@ -127,7 +211,8 @@ class WorkerClient {
|
|
|
127
211
|
body: JSON.stringify({
|
|
128
212
|
contentSessionId,
|
|
129
213
|
last_user_message: lastUserMessage,
|
|
130
|
-
last_assistant_message: lastAssistantMessage
|
|
214
|
+
last_assistant_message: lastAssistantMessage,
|
|
215
|
+
platformSource: PLATFORM_SOURCE
|
|
131
216
|
})
|
|
132
217
|
});
|
|
133
218
|
} catch {}
|
|
@@ -137,7 +222,7 @@ class WorkerClient {
|
|
|
137
222
|
await fetch(`${this.BASE_URL}/api/sessions/complete`, {
|
|
138
223
|
method: "POST",
|
|
139
224
|
headers: { "Content-Type": "application/json" },
|
|
140
|
-
body: JSON.stringify({ contentSessionId })
|
|
225
|
+
body: JSON.stringify({ contentSessionId, platformSource: PLATFORM_SOURCE })
|
|
141
226
|
});
|
|
142
227
|
} catch {}
|
|
143
228
|
}
|
|
@@ -149,15 +234,7 @@ class WorkerClient {
|
|
|
149
234
|
}
|
|
150
235
|
const contentType = response.headers.get("content-type") || "";
|
|
151
236
|
if (contentType.includes("application/json")) {
|
|
152
|
-
|
|
153
|
-
if (typeof data === "string") {
|
|
154
|
-
return data;
|
|
155
|
-
}
|
|
156
|
-
if (data && typeof data.content === "string") {
|
|
157
|
-
return data.content;
|
|
158
|
-
}
|
|
159
|
-
const text2 = JSON.stringify(data, null, 2);
|
|
160
|
-
return text2 === "{}" || text2 === "null" ? null : text2;
|
|
237
|
+
return extractTextContent(await response.json());
|
|
161
238
|
}
|
|
162
239
|
const text = await response.text();
|
|
163
240
|
return text.trim() || null;
|
|
@@ -165,6 +242,26 @@ class WorkerClient {
|
|
|
165
242
|
return null;
|
|
166
243
|
}
|
|
167
244
|
}
|
|
245
|
+
static async search(query, project, limit) {
|
|
246
|
+
try {
|
|
247
|
+
const params = new URLSearchParams({ query, project });
|
|
248
|
+
if (limit !== undefined) {
|
|
249
|
+
params.set("limit", String(limit));
|
|
250
|
+
}
|
|
251
|
+
const response = await fetch(`${this.BASE_URL}/api/search?${params.toString()}`);
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
const contentType = response.headers.get("content-type") || "";
|
|
256
|
+
if (contentType.includes("application/json")) {
|
|
257
|
+
return extractTextContent(await response.json()) || "";
|
|
258
|
+
}
|
|
259
|
+
const text = await response.text();
|
|
260
|
+
return text.trim();
|
|
261
|
+
} catch {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
168
265
|
}
|
|
169
266
|
|
|
170
267
|
// src/index.ts
|
|
@@ -176,6 +273,7 @@ var META_TOOLS = new Set([
|
|
|
176
273
|
"getmcpresource",
|
|
177
274
|
"listmcpresourcestool",
|
|
178
275
|
"listmcptools",
|
|
276
|
+
"mem-search",
|
|
179
277
|
"skill",
|
|
180
278
|
"slashcommand",
|
|
181
279
|
"todowrite"
|
|
@@ -241,7 +339,10 @@ function shouldSkipObservationTool(toolName) {
|
|
|
241
339
|
return true;
|
|
242
340
|
}
|
|
243
341
|
const normalizedName = toolName.toLowerCase();
|
|
244
|
-
|
|
342
|
+
if (normalizedName.includes("mcp-search") || normalizedName.includes("mem-search")) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
return META_TOOLS.has(normalizedName);
|
|
245
346
|
}
|
|
246
347
|
function normalizeToolOutput(output) {
|
|
247
348
|
if (typeof output === "string") {
|
|
@@ -304,19 +405,20 @@ var ClaudeMemPlugin = async (ctx) => {
|
|
|
304
405
|
const assistantBuffers = new Map;
|
|
305
406
|
async function flushAssistantBuffer(sessionId) {
|
|
306
407
|
const buf = assistantBuffers.get(sessionId);
|
|
307
|
-
if (!buf || !buf.
|
|
408
|
+
if (!buf || !buf.messageId) {
|
|
308
409
|
return;
|
|
309
410
|
}
|
|
310
411
|
if (buf.timer) {
|
|
311
412
|
clearTimeout(buf.timer);
|
|
312
413
|
buf.timer = null;
|
|
313
414
|
}
|
|
314
|
-
const {
|
|
315
|
-
buf.
|
|
415
|
+
const { messageId } = buf;
|
|
416
|
+
buf.messageId = null;
|
|
316
417
|
try {
|
|
418
|
+
const text = await fetchAssistantMessageText(sessionId, messageId);
|
|
317
419
|
const sanitized = truncateUtf8Bytes(stripTaggedContent(text), MAX_OBSERVATION_BYTES);
|
|
318
420
|
if (sanitized) {
|
|
319
|
-
await WorkerClient.sendObservation(sessionId, "assistant_message",
|
|
421
|
+
await WorkerClient.sendObservation(sessionId, "assistant_message", { messageId }, sanitized, projectRoot);
|
|
320
422
|
}
|
|
321
423
|
} catch {}
|
|
322
424
|
}
|
|
@@ -393,6 +495,23 @@ var ClaudeMemPlugin = async (ctx) => {
|
|
|
393
495
|
return { user, assistant };
|
|
394
496
|
}
|
|
395
497
|
return {
|
|
498
|
+
tool: {
|
|
499
|
+
"mem-search": tool({
|
|
500
|
+
description: "Search Claude-Mem persistent memory for this OpenCode project.",
|
|
501
|
+
args: {
|
|
502
|
+
query: tool.schema.string().min(1).describe("Search query for Claude-Mem memory"),
|
|
503
|
+
limit: tool.schema.number().int().positive().max(50).optional().describe("Maximum number of search results")
|
|
504
|
+
},
|
|
505
|
+
execute: async ({ query, limit }) => {
|
|
506
|
+
const isHealthy = await checkWorkerAndToast();
|
|
507
|
+
if (!isHealthy) {
|
|
508
|
+
return "Claude-Mem worker is offline. Start Claude-Mem and retry the search.";
|
|
509
|
+
}
|
|
510
|
+
const result = await WorkerClient.search(query, projectName, limit);
|
|
511
|
+
return result || `No Claude-Mem results found for "${query}".`;
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
},
|
|
396
515
|
event: async ({ event }) => {
|
|
397
516
|
switch (event.type) {
|
|
398
517
|
case "session.created": {
|
|
@@ -419,16 +538,11 @@ var ClaudeMemPlugin = async (ctx) => {
|
|
|
419
538
|
if (!await ensureSessionInit(sessionId)) {
|
|
420
539
|
return;
|
|
421
540
|
}
|
|
422
|
-
const text = await fetchAssistantMessageText(sessionId, messageId);
|
|
423
|
-
if (!text) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
541
|
let buf = assistantBuffers.get(sessionId);
|
|
427
542
|
if (!buf) {
|
|
428
|
-
buf = {
|
|
543
|
+
buf = { messageId: null, timer: null };
|
|
429
544
|
assistantBuffers.set(sessionId, buf);
|
|
430
545
|
}
|
|
431
|
-
buf.text = text;
|
|
432
546
|
buf.messageId = messageId;
|
|
433
547
|
scheduleAssistantFlush(sessionId);
|
|
434
548
|
return;
|
|
@@ -476,7 +590,6 @@ var ClaudeMemPlugin = async (ctx) => {
|
|
|
476
590
|
try {
|
|
477
591
|
const { user, assistant } = await fetchLastMessages(sessionId);
|
|
478
592
|
await WorkerClient.summarize(sessionId, user, assistant);
|
|
479
|
-
await WorkerClient.completeSession(sessionId);
|
|
480
593
|
await toast("Session summarized", "success", 2000);
|
|
481
594
|
} catch {}
|
|
482
595
|
return;
|
package/dist/worker-client.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
export interface WorkerEndpoint {
|
|
2
|
+
readonly host: string;
|
|
3
|
+
readonly port: number;
|
|
4
|
+
}
|
|
1
5
|
export type AutoStartResult = 'already-running' | 'started' | 'no-bun' | 'spawn-failed' | 'timeout' | 'skipped';
|
|
6
|
+
export declare function parseWorkerEndpoint(env: Record<string, string | undefined>, rawSettings: string | null): WorkerEndpoint;
|
|
2
7
|
export declare class WorkerClient {
|
|
3
|
-
private static readonly PORT;
|
|
4
8
|
private static readonly BASE_URL;
|
|
5
9
|
/** Per-process guard so multiple OpenCode windows don't all spawn workers. */
|
|
6
10
|
private static autoStartPromise;
|
|
@@ -60,4 +64,5 @@ export declare class WorkerClient {
|
|
|
60
64
|
* Uses /api/context/inject which returns rich markdown with observations + session summaries.
|
|
61
65
|
*/
|
|
62
66
|
static getContext(project: string): Promise<string | null>;
|
|
67
|
+
static search(query: string, project: string, limit?: number): Promise<string>;
|
|
63
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ephemushroom/opencode-claude-mem",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "OpenCode plugin for Claude-Mem persistent memory system",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "bun build src/index.ts --outfile dist/index.js --target bun --format esm --external @opencode-ai/plugin && tsc --emitDeclarationOnly",
|
|
22
|
+
"build": "bun build src/index.ts --outfile dist/index.js --target bun --format esm --external @opencode-ai/plugin && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
23
23
|
"dev": "tsc --watch",
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
25
|
"lint": "oxlint",
|
|
@@ -48,8 +48,10 @@
|
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@opencode-ai/plugin": "^1.3.0"
|
|
53
|
+
},
|
|
51
54
|
"devDependencies": {
|
|
52
|
-
"@opencode-ai/plugin": "^1.3.0",
|
|
53
55
|
"@types/bun": "latest",
|
|
54
56
|
"oxfmt": "^0.33.0",
|
|
55
57
|
"oxlint": "^1.48.0",
|