@rvboris/opencode-mempalace 0.4.0 → 0.5.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 +94 -25
- package/README.ru.md +94 -25
- package/dist/bridge/mempalace_adapter.py +26 -0
- package/dist/plugin/hooks/event.js +17 -6
- package/dist/plugin/lib/adapter.d.ts +21 -0
- package/dist/plugin/lib/adapter.js +180 -56
- package/dist/plugin/lib/autosave.d.ts +1 -0
- package/dist/plugin/lib/autosave.js +23 -0
- package/dist/plugin/lib/constants.d.ts +4 -0
- package/dist/plugin/lib/constants.js +8 -0
- package/dist/plugin/lib/context.d.ts +0 -1
- package/dist/plugin/lib/context.js +1 -14
- package/dist/plugin/lib/derive.d.ts +2 -0
- package/dist/plugin/lib/derive.js +21 -0
- package/dist/plugin/lib/status.d.ts +14 -3
- package/dist/plugin/lib/status.js +72 -27
- package/dist/plugin/lib/types.d.ts +51 -3
- package/dist/plugin/lib/types.js +2 -2
- package/dist/plugin/tools/mempalace-memory.d.ts +30 -1
- package/dist/plugin/tools/mempalace-memory.js +80 -0
- package/dist/plugin/tui/hud.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Before every reply, the plugin searches your memory for relevant context. After
|
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
You: "What build tool does this project use?"
|
|
17
|
-
AI: [searches memory] → "Bun.
|
|
17
|
+
AI: [searches memory] → "Bun. This project uses Bun."
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
### The result
|
|
@@ -43,6 +43,57 @@ Add to `opencode.json`:
|
|
|
43
43
|
|
|
44
44
|
That's it. Memory search, autosave, and both tools are active immediately.
|
|
45
45
|
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
The plugin runs inside OpenCode as hooks + tools. A thin Python bridge calls the local `mempalace` package, which stores everything in ChromaDB with on-device `embeddinggemma-300m` embeddings. No cloud, no API keys.
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart TD
|
|
52
|
+
subgraph OC["OpenCode"]
|
|
53
|
+
U["User"]
|
|
54
|
+
M["AI Model"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph PL["TypeScript plugin (this repo)"]
|
|
58
|
+
H1["system.transform hook — injects: search memory first"]
|
|
59
|
+
H2["event hook — autosave on idle / compact / close"]
|
|
60
|
+
T["mempalace_memory (9 modes) + mempalace_status tools"]
|
|
61
|
+
HUD["TUI HUD — session stats badge"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
subgraph BR["Python bridge"]
|
|
65
|
+
A["mempalace_adapter.py — spawned per call"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
subgraph MP["mempalace package (local)"]
|
|
69
|
+
SRV["mcp_server / convo_miner"]
|
|
70
|
+
DB[("ChromaDB")]
|
|
71
|
+
EMB["embeddinggemma-300m ONNX"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
SF[("opencode_status.json")]
|
|
75
|
+
|
|
76
|
+
U -->|"message"| H1
|
|
77
|
+
H1 -->|"retrieval nudge"| M
|
|
78
|
+
M -->|"mempalace_memory search"| T
|
|
79
|
+
T --> A --> SRV --> EMB
|
|
80
|
+
SRV --> DB
|
|
81
|
+
A --> T --> M
|
|
82
|
+
M -->|"answers with context"| U
|
|
83
|
+
T -.->|"counters"| SF
|
|
84
|
+
HUD -.->|"reads"| SF
|
|
85
|
+
M -.->|"session idle"| H2
|
|
86
|
+
H2 -.->|"mine_messages"| A
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Retrieval** — before each reply, the `system.transform` hook nudges the model to search memory first. The model calls `mempalace_memory [search]`, the bridge forwards it to `mempalace`, and results (vector + BM25) come back as context for the answer.
|
|
90
|
+
|
|
91
|
+
**Autosave** — on session idle, compaction, or close, the `event` hook mines the transcript for durable facts and saves them to the right memory area via `mine_messages`. Counters are written to `opencode_status.json`, which the TUI HUD reads.
|
|
92
|
+
|
|
93
|
+
**Keyword save** — when the user says "remember this" / "note that", the plugin arms a save instruction so the model persists the fact immediately via `mempalace_memory [save]`.
|
|
94
|
+
|
|
95
|
+
The plugin talks to `mempalace` through a local Python bridge (`bridge/mempalace_adapter.py`), spawned per call — it does **not** require the MemPalace MCP server.
|
|
96
|
+
|
|
46
97
|
## Features
|
|
47
98
|
|
|
48
99
|
### Hidden retrieval
|
|
@@ -51,18 +102,27 @@ Before each answer, the plugin injects a search instruction so the model checks
|
|
|
51
102
|
|
|
52
103
|
### Background autosave
|
|
53
104
|
|
|
54
|
-
On session idle, compaction, or close, the plugin mines the conversation transcript for durable facts and saves them to the right memory area automatically.
|
|
105
|
+
On session idle, compaction, or close, the plugin mines the conversation transcript for durable facts and saves them to the right memory area automatically. Low-signal fragments are filtered before mining, so prompt leftovers like `re.`, `ls>`, or mostly punctuation are skipped instead of becoming junk memory.
|
|
106
|
+
|
|
107
|
+
### Reliable local bridge
|
|
108
|
+
|
|
109
|
+
Write-like operations (`save`, autosave mining, diary writes, graph writes, checkpoints, deletes) are serialized through the adapter and retried on MemPalace palace-lock contention (`held by PID`). Search calls still run without that write queue.
|
|
55
110
|
|
|
56
111
|
### `mempalace_memory` — the one tool
|
|
57
112
|
|
|
58
|
-
|
|
113
|
+
Nine modes, one interface:
|
|
59
114
|
|
|
60
115
|
| Mode | Purpose |
|
|
61
116
|
|---|---|
|
|
62
117
|
| `save` | Store a preference, fact, or decision |
|
|
63
|
-
| `search` | Find relevant memory by query |
|
|
118
|
+
| `search` | Find relevant memory by query (optional `source_file` filter) |
|
|
64
119
|
| `kg_add` | Add a structured fact to the knowledge graph |
|
|
65
120
|
| `diary_write` | Save a short work note |
|
|
121
|
+
| `checkpoint` | Batch-save multiple items + optional diary in one call |
|
|
122
|
+
| `delete` | Remove a memory by drawer ID |
|
|
123
|
+
| `delete_by_source` | Bulk-remove memories by source file (dry-run by default) |
|
|
124
|
+
| `kg_query` | Search the knowledge graph for an entity's relationships |
|
|
125
|
+
| `diary_read` | Read recent diary entries |
|
|
66
126
|
|
|
67
127
|
Examples:
|
|
68
128
|
|
|
@@ -78,6 +138,14 @@ mempalace_memory mode: search scope: project room: decisions query: build to
|
|
|
78
138
|
mempalace_memory mode: kg_add subject: my-repo predicate: uses object: bun
|
|
79
139
|
```
|
|
80
140
|
|
|
141
|
+
```text
|
|
142
|
+
mempalace_memory mode: checkpoint items: [{"wing":"wing_user","room":"preferences","content":"likes dark mode"},{"wing":"wing_project","room":"decisions","content":"uses bun"}]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
mempalace_memory mode: delete_by_source source_file: /data/import.jsonl dry_run: true
|
|
147
|
+
```
|
|
148
|
+
|
|
81
149
|
### `mempalace_status` — visible proof
|
|
82
150
|
|
|
83
151
|
Check whether the plugin is actually helping:
|
|
@@ -93,10 +161,26 @@ Shows retrieval hit rate, last autosave outcome, memory previews, and cumulative
|
|
|
93
161
|
A compact session stats line appears in the OpenCode prompt area:
|
|
94
162
|
|
|
95
163
|
```
|
|
96
|
-
MEM
|
|
164
|
+
MEM helps 3
|
|
165
|
+
MEM cited 2
|
|
166
|
+
MEM found 5
|
|
167
|
+
MEM no hits
|
|
168
|
+
MEM searched
|
|
169
|
+
MEM quiet
|
|
170
|
+
MEM helps 1 · fail 1
|
|
97
171
|
```
|
|
98
172
|
|
|
99
|
-
|
|
173
|
+
- `MEM helps N` — memory improved or saved time (the most useful verdicts)
|
|
174
|
+
- `MEM cited N` — memory was mentioned but did not change the answer
|
|
175
|
+
- `MEM no help` — retrieval happened but had no effect
|
|
176
|
+
- `MEM unknown` — model omitted the verdict tag
|
|
177
|
+
- `MEM found N` — retrieval returned N memories and no judge verdict is recorded yet
|
|
178
|
+
- `MEM no hits` — retrieval ran and returned no memories
|
|
179
|
+
- `MEM searched` — retrieval ran, but result count is unavailable
|
|
180
|
+
- `MEM quiet` — no retrieval activity yet
|
|
181
|
+
- `· fail N` / `· skip N` — shown only when autosave has errors
|
|
182
|
+
|
|
183
|
+
The HUD combines retrieval evidence with a **judge signal**. When the model reports `[memory: verdict]`, the plugin parses it after each turn and strips it before saving. If no verdict is available, retrieval results still show as `found`, `no hits`, or `searched`. Requires a `tui.json` entry (see below).
|
|
100
184
|
|
|
101
185
|
## Memory areas
|
|
102
186
|
|
|
@@ -154,7 +238,7 @@ To enable the prompt-area stats display, add a `tui.json` in your OpenCode confi
|
|
|
154
238
|
{
|
|
155
239
|
"$schema": "https://opencode.ai/tui.json",
|
|
156
240
|
"plugin": [
|
|
157
|
-
"file:///path/to/mempalace
|
|
241
|
+
"file:///path/to/opencode-mempalace/plugin/tui/index.tsx"
|
|
158
242
|
]
|
|
159
243
|
}
|
|
160
244
|
```
|
|
@@ -168,21 +252,6 @@ Or when installed from npm, use the package entry:
|
|
|
168
252
|
}
|
|
169
253
|
```
|
|
170
254
|
|
|
171
|
-
## How it works
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
User message
|
|
175
|
-
→ system hook injects "search memory first" instruction
|
|
176
|
-
→ model calls mempalace_memory [search]
|
|
177
|
-
→ results inform the answer
|
|
178
|
-
|
|
179
|
-
Session ends / idles
|
|
180
|
-
→ event hook mines transcript
|
|
181
|
-
→ Python adapter saves durable facts via MemPalace
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
The plugin uses a local Python bridge (`bridge/mempalace_adapter.py`) to communicate with MemPalace. It does **not** require the MemPalace MCP server.
|
|
185
|
-
|
|
186
255
|
## Compatibility
|
|
187
256
|
|
|
188
257
|
| Requirement | Version |
|
|
@@ -194,14 +263,14 @@ The plugin uses a local Python bridge (`bridge/mempalace_adapter.py`) to communi
|
|
|
194
263
|
|
|
195
264
|
## Project docs
|
|
196
265
|
|
|
197
|
-
- [Changelog](./CHANGELOG.md) — release
|
|
266
|
+
- [Changelog](./CHANGELOG.md) — release notes
|
|
198
267
|
- [Contributing](./CONTRIBUTING.md) — changelog rules
|
|
199
268
|
|
|
200
269
|
## Local development
|
|
201
270
|
|
|
202
271
|
```bash
|
|
203
272
|
git clone https://github.com/rvboris/opencode-mempalace.git
|
|
204
|
-
cd opencode-mempalace
|
|
273
|
+
cd opencode-mempalace
|
|
205
274
|
npm install
|
|
206
275
|
npm run build
|
|
207
276
|
```
|
|
@@ -210,7 +279,7 @@ Load from source in `opencode.json`:
|
|
|
210
279
|
|
|
211
280
|
```jsonc
|
|
212
281
|
{
|
|
213
|
-
"plugin": ["file:///ABSOLUTE/PATH/TO/mempalace
|
|
282
|
+
"plugin": ["file:///ABSOLUTE/PATH/TO/opencode-mempalace/plugin/index.ts"]
|
|
214
283
|
}
|
|
215
284
|
```
|
|
216
285
|
|
package/README.ru.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
Ты: "Какую систему сборки мы используем?"
|
|
17
|
-
ИИ: [ищет в памяти] → "Bun.
|
|
17
|
+
ИИ: [ищет в памяти] → "Bun. Этот проект использует Bun."
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
### Результат
|
|
@@ -43,6 +43,57 @@ mempalace init ~/.mempalace/palace
|
|
|
43
43
|
|
|
44
44
|
Готово. Поиск по памяти, автосохранение и оба инструмента активны сразу.
|
|
45
45
|
|
|
46
|
+
## Как это работает
|
|
47
|
+
|
|
48
|
+
Плагин работает внутри OpenCode как набор хуков и тулов. Тонкий Python-мост вызывает локальный пакет `mempalace`, который хранит всё в ChromaDB с локальными эмбеддингами `embeddinggemma-300m` на устройстве. Без облака, без API-ключей.
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart TD
|
|
52
|
+
subgraph OC["OpenCode"]
|
|
53
|
+
U["Пользователь"]
|
|
54
|
+
M["ИИ-модель"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph PL["TypeScript-плагин (этот репо)"]
|
|
58
|
+
H1["system.transform хук — подставляет: сначала ищи в памяти"]
|
|
59
|
+
H2["event хук — автосохранение при простое / компактизации / закрытии"]
|
|
60
|
+
T["тулы mempalace_memory (9 режимов) + mempalace_status"]
|
|
61
|
+
HUD["TUI HUD — бейдж статистики сессии"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
subgraph BR["Python-мост"]
|
|
65
|
+
A["mempalace_adapter.py — запускается на каждый вызов"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
subgraph MP["пакет mempalace (локально)"]
|
|
69
|
+
SRV["mcp_server / convo_miner"]
|
|
70
|
+
DB[("ChromaDB")]
|
|
71
|
+
EMB["embeddinggemma-300m ONNX"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
SF[("opencode_status.json")]
|
|
75
|
+
|
|
76
|
+
U -->|"сообщение"| H1
|
|
77
|
+
H1 -->|"подсказка поиска"| M
|
|
78
|
+
M -->|"mempalace_memory search"| T
|
|
79
|
+
T --> A --> SRV --> EMB
|
|
80
|
+
SRV --> DB
|
|
81
|
+
A --> T --> M
|
|
82
|
+
M -->|"ответ с контекстом"| U
|
|
83
|
+
T -.->|"счётчики"| SF
|
|
84
|
+
HUD -.->|"читает"| SF
|
|
85
|
+
M -.->|"простой сессии"| H2
|
|
86
|
+
H2 -.->|"mine_messages"| A
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Поиск (retrieval)** — перед каждым ответом хук `system.transform` подталкивает модель сначала проверить память. Модель вызывает `mempalace_memory [search]`, мост прокидывает вызов в `mempalace`, результаты (векторный + BM25-поиск) возвращаются как контекст для ответа.
|
|
90
|
+
|
|
91
|
+
**Автосохранение** — при простое сессии, компактизации или закрытии хук `event` анализирует транскрипт, извлекает устойчивые факты и сохраняет их в нужную область памяти через `mine_messages`. Счётчики пишутся в `opencode_status.json`, который читает TUI HUD.
|
|
92
|
+
|
|
93
|
+
**Сохранение по ключевому слову** — когда пользователь говорит «запомни это» / «отметь», плагин активирует инструкцию сохранения, чтобы модель сразу сохранила факт через `mempalace_memory [save]`.
|
|
94
|
+
|
|
95
|
+
Плагин обращается к `mempalace` через локальный Python-мост (`bridge/mempalace_adapter.py`), запускаемый на каждый вызов, — MCP-сервер MemPalace ему **не нужен**.
|
|
96
|
+
|
|
46
97
|
## Возможности
|
|
47
98
|
|
|
48
99
|
### Скрытый поиск
|
|
@@ -51,18 +102,27 @@ mempalace init ~/.mempalace/palace
|
|
|
51
102
|
|
|
52
103
|
### Фоновое автосохранение
|
|
53
104
|
|
|
54
|
-
При простое сессии, компактизации или завершении — плагин анализирует транскрипт, извлекает устойчивые факты и сохраняет их в нужную область памяти.
|
|
105
|
+
При простое сессии, компактизации или завершении — плагин анализирует транскрипт, извлекает устойчивые факты и сохраняет их в нужную область памяти. Низкосигнальные фрагменты фильтруются до майнинга, поэтому остатки вроде `re.`, `ls>` или почти одна пунктуация не становятся мусорной памятью.
|
|
106
|
+
|
|
107
|
+
### Надёжный локальный мост
|
|
108
|
+
|
|
109
|
+
Операции записи (`save`, автосохранение, diary, граф, checkpoints, удаления) проходят через очередь адаптера и повторяются при конфликте palace-lock (`held by PID`). Поиск выполняется без этой очереди.
|
|
55
110
|
|
|
56
111
|
### `mempalace_memory` — единый инструмент
|
|
57
112
|
|
|
58
|
-
|
|
113
|
+
Девять режимов, один интерфейс:
|
|
59
114
|
|
|
60
115
|
| Режим | Назначение |
|
|
61
116
|
|---|---|
|
|
62
117
|
| `save` | Сохранить предпочтение, факт или решение |
|
|
63
|
-
| `search` | Найти релевантную память по запросу |
|
|
118
|
+
| `search` | Найти релевантную память по запросу (опциональный фильтр `source_file`) |
|
|
64
119
|
| `kg_add` | Добавить структурированный факт в граф знаний |
|
|
65
120
|
| `diary_write` | Записать короткую рабочую заметку |
|
|
121
|
+
| `checkpoint` | Пакетное сохранение нескольких записей + опциональной diary за один вызов |
|
|
122
|
+
| `delete` | Удалить память по ID записи |
|
|
123
|
+
| `delete_by_source` | Массовое удаление памяти по файлу-источнику (dry-run по умолчанию) |
|
|
124
|
+
| `kg_query` | Поиск по графу знаний — связи сущности |
|
|
125
|
+
| `diary_read` | Чтение недавних записей diary |
|
|
66
126
|
|
|
67
127
|
Примеры:
|
|
68
128
|
|
|
@@ -78,6 +138,14 @@ mempalace_memory mode: search scope: project room: decisions query: сист
|
|
|
78
138
|
mempalace_memory mode: kg_add subject: my-repo predicate: uses object: bun
|
|
79
139
|
```
|
|
80
140
|
|
|
141
|
+
```text
|
|
142
|
+
mempalace_memory mode: checkpoint items: [{"wing":"wing_user","room":"preferences","content":"любит тёмную тему"},{"wing":"wing_project","room":"decisions","content":"использует bun"}]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
mempalace_memory mode: delete_by_source source_file: /data/import.jsonl dry_run: true
|
|
147
|
+
```
|
|
148
|
+
|
|
81
149
|
### `mempalace_status` — видимое доказательство
|
|
82
150
|
|
|
83
151
|
Проверь, реально ли плагин помогает:
|
|
@@ -93,10 +161,26 @@ mempalace_status
|
|
|
93
161
|
Компактная строка статистики текущей сессии в области ввода OpenCode:
|
|
94
162
|
|
|
95
163
|
```
|
|
96
|
-
MEM
|
|
164
|
+
MEM helps 3
|
|
165
|
+
MEM cited 2
|
|
166
|
+
MEM found 5
|
|
167
|
+
MEM no hits
|
|
168
|
+
MEM searched
|
|
169
|
+
MEM quiet
|
|
170
|
+
MEM helps 1 · fail 1
|
|
97
171
|
```
|
|
98
172
|
|
|
99
|
-
|
|
173
|
+
- `MEM helps N` — память улучшила ответ или сэкономила время (самые полезные вердикты)
|
|
174
|
+
- `MEM cited N` — память была упомянута, но ответ не изменился
|
|
175
|
+
- `MEM no help` — поиск был, но без эффекта
|
|
176
|
+
- `MEM unknown` — модель не поставила тег с вердиктом
|
|
177
|
+
- `MEM found N` — поиск вернул N записей, но judge-вердикт ещё не записан
|
|
178
|
+
- `MEM no hits` — поиск был, но ничего не нашёл
|
|
179
|
+
- `MEM searched` — поиск был, но количество результатов недоступно
|
|
180
|
+
- `MEM quiet` — поиск по памяти ещё не выполнялся
|
|
181
|
+
- `· fail N` / `· skip N` — показываются только при ошибках автосохранения
|
|
182
|
+
|
|
183
|
+
HUD совмещает факт поиска и **judge-сигнал**. Когда модель сообщает `[memory: verdict]`, плагин парсит тег после хода и вырезает перед сохранением. Если вердикта нет, результаты поиска всё равно видны как `found`, `no hits` или `searched`. Требует записи в `tui.json` (см. ниже).
|
|
100
184
|
|
|
101
185
|
## Области памяти
|
|
102
186
|
|
|
@@ -154,7 +238,7 @@ MEM hits 3 · saved 2 · failed 0 · writes 1
|
|
|
154
238
|
{
|
|
155
239
|
"$schema": "https://opencode.ai/tui.json",
|
|
156
240
|
"plugin": [
|
|
157
|
-
"file:///путь/до/mempalace
|
|
241
|
+
"file:///путь/до/opencode-mempalace/plugin/tui/index.tsx"
|
|
158
242
|
]
|
|
159
243
|
}
|
|
160
244
|
```
|
|
@@ -168,21 +252,6 @@ MEM hits 3 · saved 2 · failed 0 · writes 1
|
|
|
168
252
|
}
|
|
169
253
|
```
|
|
170
254
|
|
|
171
|
-
## Как это работает
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
Сообщение пользователя
|
|
175
|
-
→ системный хук подставляет «сначала проверь память»
|
|
176
|
-
→ модель вызывает mempalace_memory [search]
|
|
177
|
-
→ результаты влияют на ответ
|
|
178
|
-
|
|
179
|
-
Сессия завершается / простаивает
|
|
180
|
-
→ хук событий анализирует транскрипт
|
|
181
|
-
→ Python-адаптер сохраняет устойчивые факты через MemPalace
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Плагин использует локальную Python-прослойку (`bridge/mempalace_adapter.py`) для работы с MemPalace. Ему **не нужен** MCP-сервер MemPalace.
|
|
185
|
-
|
|
186
255
|
## Совместимость
|
|
187
256
|
|
|
188
257
|
| Требование | Версия |
|
|
@@ -194,14 +263,14 @@ MEM hits 3 · saved 2 · failed 0 · writes 1
|
|
|
194
263
|
|
|
195
264
|
## Документация проекта
|
|
196
265
|
|
|
197
|
-
- [Changelog](./CHANGELOG.md) —
|
|
266
|
+
- [Changelog](./CHANGELOG.md) — заметки к релизам
|
|
198
267
|
- [Contributing](./CONTRIBUTING.md) — правила ведения changelog
|
|
199
268
|
|
|
200
269
|
## Локальная разработка
|
|
201
270
|
|
|
202
271
|
```bash
|
|
203
272
|
git clone https://github.com/rvboris/opencode-mempalace.git
|
|
204
|
-
cd opencode-mempalace
|
|
273
|
+
cd opencode-mempalace
|
|
205
274
|
npm install
|
|
206
275
|
npm run build
|
|
207
276
|
```
|
|
@@ -210,7 +279,7 @@ npm run build
|
|
|
210
279
|
|
|
211
280
|
```jsonc
|
|
212
281
|
{
|
|
213
|
-
"plugin": ["file:///ABSOLUTE/PATH/TO/mempalace
|
|
282
|
+
"plugin": ["file:///ABSOLUTE/PATH/TO/opencode-mempalace/plugin/index.ts"]
|
|
214
283
|
}
|
|
215
284
|
```
|
|
216
285
|
|
|
@@ -37,6 +37,7 @@ def main() -> int:
|
|
|
37
37
|
limit=payload.get("limit", 5),
|
|
38
38
|
wing=payload.get("wing"),
|
|
39
39
|
room=payload.get("room"),
|
|
40
|
+
source_file=payload.get("source_file"),
|
|
40
41
|
)
|
|
41
42
|
elif mode == "save":
|
|
42
43
|
result = mcp_server.tool_add_drawer(
|
|
@@ -78,6 +79,31 @@ def main() -> int:
|
|
|
78
79
|
"mode": "mine_messages",
|
|
79
80
|
"wing": payload.get("wing"),
|
|
80
81
|
}
|
|
82
|
+
elif mode == "delete":
|
|
83
|
+
result = mcp_server.tool_delete_drawer(drawer_id=payload["drawer_id"])
|
|
84
|
+
elif mode == "delete_by_source":
|
|
85
|
+
result = mcp_server.tool_delete_by_source(
|
|
86
|
+
source_file=payload["source_file"],
|
|
87
|
+
dry_run=payload.get("dry_run", True),
|
|
88
|
+
)
|
|
89
|
+
elif mode == "kg_query":
|
|
90
|
+
result = mcp_server.tool_kg_query(
|
|
91
|
+
entity=payload["entity"],
|
|
92
|
+
as_of=payload.get("as_of"),
|
|
93
|
+
direction=payload.get("direction", "both"),
|
|
94
|
+
)
|
|
95
|
+
elif mode == "diary_read":
|
|
96
|
+
result = mcp_server.tool_diary_read(
|
|
97
|
+
agent_name=payload.get("agent_name", "opencode"),
|
|
98
|
+
last_n=payload.get("last_n", 10),
|
|
99
|
+
wing=payload.get("wing", ""),
|
|
100
|
+
)
|
|
101
|
+
elif mode == "checkpoint":
|
|
102
|
+
result = mcp_server.tool_checkpoint(
|
|
103
|
+
items=payload["items"],
|
|
104
|
+
diary=payload.get("diary"),
|
|
105
|
+
dedup_threshold=payload.get("dedup_threshold", 0.9),
|
|
106
|
+
)
|
|
81
107
|
else:
|
|
82
108
|
result = {"success": False, "error": f"Unknown mode: {mode}"}
|
|
83
109
|
except Exception as error:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { AutosaveReason, AutosaveStatus, buildMessageSnapshot, getMessageSnapshot, getSessionState, markAutosaveComplete, markKeywordSavePending, markFailed, markRetrievalPending, setMessageSnapshot, shouldScheduleAutosave, } from "../lib/autosave";
|
|
1
|
+
import { AutosaveReason, AutosaveStatus, buildAutosaveMiningTranscript, buildMessageSnapshot, getMessageSnapshot, getSessionState, markAutosaveComplete, markKeywordSavePending, markFailed, markRetrievalPending, setMessageSnapshot, shouldScheduleAutosave, } from "../lib/autosave";
|
|
2
2
|
import { executeAdapter } from "../lib/adapter";
|
|
3
3
|
import { loadConfig } from "../lib/config";
|
|
4
4
|
import { COMPACTION_CONTEXT_MESSAGE, DEFAULT_AGENT_NAME, LOG_MESSAGES } from "../lib/constants";
|
|
5
|
-
import { sanitizeText } from "../lib/derive";
|
|
5
|
+
import { sanitizeText, stripJudgeTag, parseJudgeTag } from "../lib/derive";
|
|
6
6
|
import { getProjectName, loadSessionMessages } from "../lib/opencode";
|
|
7
7
|
import { redactSecrets } from "../lib/privacy";
|
|
8
8
|
import { getProjectScope } from "../lib/scope";
|
|
9
|
-
import { recordAutosave } from "../lib/status";
|
|
9
|
+
import { recordAutosave, recordRetrievalJudge } from "../lib/status";
|
|
10
10
|
import { writeLog } from "../lib/log";
|
|
11
11
|
import { SESSION_EVENT_TYPES } from "../lib/types";
|
|
12
12
|
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -61,6 +61,16 @@ export const eventHooks = (ctx) => {
|
|
|
61
61
|
}
|
|
62
62
|
if (!isAutosaveTriggerEventType(event.type))
|
|
63
63
|
return;
|
|
64
|
+
// Parse judge tag from last assistant message before anything else.
|
|
65
|
+
// Only when a retrieval happened this turn (retrievalPending was set).
|
|
66
|
+
const state = getSessionState(sessionId);
|
|
67
|
+
if (state.retrievalPending) {
|
|
68
|
+
const snapshot = getMessageSnapshot(sessionId);
|
|
69
|
+
if (snapshot) {
|
|
70
|
+
const verdict = (parseJudgeTag(snapshot.transcript) ?? "unknown");
|
|
71
|
+
await recordRetrievalJudge({ sessionId, verdict });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
64
74
|
await writeLog("INFO", LOG_MESSAGES.autosaveTriggerReceived, {
|
|
65
75
|
eventType: event.type,
|
|
66
76
|
sessionId,
|
|
@@ -103,8 +113,9 @@ export const eventHooks = (ctx) => {
|
|
|
103
113
|
});
|
|
104
114
|
return;
|
|
105
115
|
}
|
|
106
|
-
const sanitizedTranscript = sanitizeText(redactSecrets(transcript));
|
|
107
|
-
|
|
116
|
+
const sanitizedTranscript = sanitizeText(redactSecrets(stripJudgeTag(transcript)));
|
|
117
|
+
const miningTranscript = buildAutosaveMiningTranscript(sanitizedTranscript);
|
|
118
|
+
if (!miningTranscript) {
|
|
108
119
|
markAutosaveComplete(sessionId, userDigest, transcriptDigest, AutosaveStatus.Noop);
|
|
109
120
|
await recordAutosave({
|
|
110
121
|
sessionId,
|
|
@@ -118,7 +129,7 @@ export const eventHooks = (ctx) => {
|
|
|
118
129
|
const wing = getProjectScope(getProjectName(ctx.project), config.projectWingPrefix).wing;
|
|
119
130
|
const result = await executeAdapter(ctx.$, {
|
|
120
131
|
mode: "mine_messages",
|
|
121
|
-
transcript:
|
|
132
|
+
transcript: miningTranscript,
|
|
122
133
|
wing,
|
|
123
134
|
extract_mode: config.autoMineExtractMode,
|
|
124
135
|
agent: DEFAULT_AGENT_NAME,
|
|
@@ -1,2 +1,23 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
1
2
|
import type { AdapterRequest, AdapterResponse } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the Python interpreter that owns the `mempalace` package.
|
|
5
|
+
*
|
|
6
|
+
* Resolution order:
|
|
7
|
+
* 1. `MEMPALACE_ADAPTER_PYTHON` env (explicit override).
|
|
8
|
+
* 2. The shebang of the `mempalace` CLI on $PATH — a console_script always
|
|
9
|
+
* points at the interpreter that installed the package (pipx, uv-tool,
|
|
10
|
+
* pip --user, or a venv), so this is the most reliable auto-detection.
|
|
11
|
+
* 3. `python3` then `python` on $PATH (best-effort; may still lack mempalace).
|
|
12
|
+
*
|
|
13
|
+
* Returns null if nothing usable was found.
|
|
14
|
+
*/
|
|
15
|
+
export declare const resolvePython: () => string | null;
|
|
16
|
+
/** Test-only: clear the cached interpreter so env/PATH changes are re-read. */
|
|
17
|
+
export declare const resetPythonResolver: () => void;
|
|
18
|
+
declare let adapterDelay: (ms: number) => Promise<void>;
|
|
19
|
+
export declare const setAdapterDelayForTests: (delay: typeof adapterDelay) => void;
|
|
20
|
+
export declare const setAdapterSpawnForTests: (spawn: typeof nodeSpawn) => void;
|
|
21
|
+
export declare const resetAdapterTestHooks: () => void;
|
|
2
22
|
export declare const executeAdapter: (_shell: unknown, payload: AdapterRequest, retries?: number) => Promise<AdapterResponse>;
|
|
23
|
+
export {};
|