@zilliz/memsearch-opencode 0.2.0 → 0.2.1
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
|
@@ -143,6 +143,14 @@ memsearch config set embedding.provider openai
|
|
|
143
143
|
export OPENAI_API_KEY=sk-...
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
To override only the OpenCode capture summarization model:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
memsearch config set plugins.opencode.summarize.model anthropic/claude-haiku
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Leave it empty or unset to keep the current `small_model` / plugin default behavior. This setting does not fall back to `llm.model`.
|
|
153
|
+
|
|
146
154
|
## How It Works
|
|
147
155
|
|
|
148
156
|
1. **Capture**: After each conversation turn, the plugin extracts the user+assistant exchange, summarizes it via LLM, and appends to a daily markdown file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zilliz/memsearch-opencode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "memsearch plugin for OpenCode — semantic memory search across sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"index.ts",
|
|
12
|
-
"scripts
|
|
12
|
+
"scripts/*.py",
|
|
13
|
+
"scripts/*.sh",
|
|
13
14
|
"skills/",
|
|
14
15
|
"prompts/",
|
|
15
16
|
"README.md"
|
|
@@ -11,19 +11,26 @@ extracts the last turn, summarizes it as bullet points, and writes to
|
|
|
11
11
|
It also triggers memsearch indexing after writing.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import argparse
|
|
15
|
+
import contextlib
|
|
15
16
|
import json
|
|
16
|
-
import sys
|
|
17
17
|
import os
|
|
18
|
-
import
|
|
18
|
+
import shlex
|
|
19
19
|
import shutil
|
|
20
|
-
import subprocess
|
|
21
|
-
import argparse
|
|
22
20
|
import signal
|
|
21
|
+
import sqlite3
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
23
25
|
from datetime import datetime
|
|
24
26
|
from pathlib import Path
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def split_memsearch_cmd(memsearch_cmd):
|
|
30
|
+
"""Split the configured memsearch command preserving quoted arguments."""
|
|
31
|
+
return shlex.split(memsearch_cmd)
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
def get_small_model():
|
|
28
35
|
"""Read small_model from opencode.json config (fallback to model, then empty)."""
|
|
29
36
|
config_paths = [
|
|
@@ -41,6 +48,20 @@ def get_small_model():
|
|
|
41
48
|
return ""
|
|
42
49
|
|
|
43
50
|
|
|
51
|
+
def get_plugin_summarize_model(memsearch_cmd=None):
|
|
52
|
+
"""Read the memsearch OpenCode summarize model override."""
|
|
53
|
+
if not memsearch_cmd:
|
|
54
|
+
return ""
|
|
55
|
+
try:
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
[*split_memsearch_cmd(memsearch_cmd), "config", "get", "plugins.opencode.summarize.model"],
|
|
58
|
+
capture_output=True, text=True, timeout=5,
|
|
59
|
+
)
|
|
60
|
+
return result.stdout.strip()
|
|
61
|
+
except Exception:
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
|
|
44
65
|
def ensure_isolated_config():
|
|
45
66
|
"""Create isolated config dir without plugins/ to prevent recursion."""
|
|
46
67
|
isolated = "/tmp/opencode-memsearch-summarize/opencode"
|
|
@@ -48,6 +69,9 @@ def ensure_isolated_config():
|
|
|
48
69
|
# Copy opencode.json (provider config) but NOT plugins/
|
|
49
70
|
src = os.path.expanduser("~/.config/opencode/opencode.json")
|
|
50
71
|
dst = os.path.join(isolated, "opencode.json")
|
|
72
|
+
if os.path.islink(dst) or (os.path.lexists(dst) and not os.path.isfile(dst)):
|
|
73
|
+
with contextlib.suppress(OSError):
|
|
74
|
+
os.remove(dst)
|
|
51
75
|
if os.path.exists(src) and not os.path.exists(dst):
|
|
52
76
|
shutil.copy2(src, dst)
|
|
53
77
|
return os.path.dirname(isolated) # /tmp/opencode-memsearch-summarize
|
|
@@ -59,7 +83,7 @@ def _load_summarize_prompt(agent_name, memsearch_cmd=None):
|
|
|
59
83
|
if memsearch_cmd:
|
|
60
84
|
try:
|
|
61
85
|
result = subprocess.run(
|
|
62
|
-
memsearch_cmd
|
|
86
|
+
[*split_memsearch_cmd(memsearch_cmd), "config", "get", "prompts.summarize"],
|
|
63
87
|
capture_output=True, text=True, timeout=5,
|
|
64
88
|
)
|
|
65
89
|
custom_path = result.stdout.strip()
|
|
@@ -91,9 +115,10 @@ def summarize_with_llm(turn_text, small_model, memsearch_cmd=None):
|
|
|
91
115
|
)
|
|
92
116
|
isolated_dir = ensure_isolated_config()
|
|
93
117
|
|
|
118
|
+
summarize_model = get_plugin_summarize_model(memsearch_cmd) or small_model
|
|
94
119
|
cmd = ["opencode", "run"]
|
|
95
|
-
if
|
|
96
|
-
cmd += ["-m",
|
|
120
|
+
if summarize_model:
|
|
121
|
+
cmd += ["-m", summarize_model]
|
|
97
122
|
cmd.append(full_prompt)
|
|
98
123
|
|
|
99
124
|
try:
|
|
@@ -110,7 +135,7 @@ def summarize_with_llm(turn_text, small_model, memsearch_cmd=None):
|
|
|
110
135
|
output = result.stdout.strip()
|
|
111
136
|
# Extract bullet points (skip any opencode run header lines)
|
|
112
137
|
lines = output.split("\n")
|
|
113
|
-
bullets = [
|
|
138
|
+
bullets = [line for line in lines if line.strip().startswith("- ")]
|
|
114
139
|
if bullets:
|
|
115
140
|
return "\n".join(bullets)
|
|
116
141
|
except Exception:
|
|
@@ -260,7 +285,7 @@ def main():
|
|
|
260
285
|
|
|
261
286
|
db_path = get_db_path()
|
|
262
287
|
if not os.path.exists(db_path):
|
|
263
|
-
|
|
288
|
+
sys.stderr.write(f"OpenCode database not found at {db_path}\n")
|
|
264
289
|
sys.exit(1)
|
|
265
290
|
|
|
266
291
|
memory_dir = os.path.join(args.project_dir, ".memsearch", "memory")
|
|
@@ -273,10 +298,8 @@ def main():
|
|
|
273
298
|
|
|
274
299
|
# Clean up on exit
|
|
275
300
|
def cleanup(signum=None, frame=None):
|
|
276
|
-
|
|
301
|
+
with contextlib.suppress(OSError):
|
|
277
302
|
os.remove(pid_file)
|
|
278
|
-
except OSError:
|
|
279
|
-
pass
|
|
280
303
|
sys.exit(0)
|
|
281
304
|
|
|
282
305
|
signal.signal(signal.SIGTERM, cleanup)
|
|
@@ -287,10 +310,8 @@ def main():
|
|
|
287
310
|
state_file = os.path.join(args.project_dir, ".memsearch", ".last_msg_time")
|
|
288
311
|
last_msg_time = 0
|
|
289
312
|
if os.path.exists(state_file):
|
|
290
|
-
|
|
291
|
-
last_msg_time = int(
|
|
292
|
-
except (ValueError, OSError):
|
|
293
|
-
pass
|
|
313
|
+
with contextlib.suppress(ValueError, OSError), open(state_file) as f:
|
|
314
|
+
last_msg_time = int(f.read().strip())
|
|
294
315
|
|
|
295
316
|
while True:
|
|
296
317
|
try:
|
|
Binary file
|