@rvboris/opencode-mempalace 0.1.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/LICENSE +21 -0
- package/README.md +230 -0
- package/README.ru.md +230 -0
- package/dist/bridge/mempalace_adapter.py +54 -0
- package/dist/plugin/hooks/chat-params.d.ts +5 -0
- package/dist/plugin/hooks/chat-params.js +8 -0
- package/dist/plugin/hooks/event.d.ts +19 -0
- package/dist/plugin/hooks/event.js +110 -0
- package/dist/plugin/hooks/system.d.ts +10 -0
- package/dist/plugin/hooks/system.js +50 -0
- package/dist/plugin/hooks/tool.d.ts +17 -0
- package/dist/plugin/hooks/tool.js +61 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.js +18 -0
- package/dist/plugin/lib/adapter.d.ts +1 -0
- package/dist/plugin/lib/adapter.js +42 -0
- package/dist/plugin/lib/autosave.d.ts +52 -0
- package/dist/plugin/lib/autosave.js +242 -0
- package/dist/plugin/lib/config.d.ts +13 -0
- package/dist/plugin/lib/config.js +53 -0
- package/dist/plugin/lib/context.d.ts +12 -0
- package/dist/plugin/lib/context.js +37 -0
- package/dist/plugin/lib/derive.d.ts +1 -0
- package/dist/plugin/lib/derive.js +36 -0
- package/dist/plugin/lib/enforcement.d.ts +1 -0
- package/dist/plugin/lib/enforcement.js +10 -0
- package/dist/plugin/lib/log.d.ts +11 -0
- package/dist/plugin/lib/log.js +48 -0
- package/dist/plugin/lib/privacy.d.ts +3 -0
- package/dist/plugin/lib/privacy.js +19 -0
- package/dist/plugin/lib/scope.d.ts +8 -0
- package/dist/plugin/lib/scope.js +16 -0
- package/dist/plugin/tools/mempalace-memory.d.ts +42 -0
- package/dist/plugin/tools/mempalace-memory.js +89 -0
- package/example-opencode.json +4 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rvboris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# OpenCode MemPalace Plugin
|
|
2
|
+
|
|
3
|
+
> Persistent memory for OpenCode with hidden retrieval, autosave, and a safe MemPalace wrapper tool.
|
|
4
|
+
|
|
5
|
+
[Русская версия](./README.ru.md)
|
|
6
|
+
|
|
7
|
+
OpenCode plugin for hidden retrieval and autosave with [MemPalace](https://github.com/milla-jovovich/mempalace) through a local Python adapter.
|
|
8
|
+
|
|
9
|
+
- OpenCode: https://opencode.ai
|
|
10
|
+
- MemPalace: https://github.com/milla-jovovich/mempalace
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
- injects hidden retrieval hints before normal answers
|
|
15
|
+
- marks autosave on session lifecycle events
|
|
16
|
+
- exposes one safe memory tool: `mempalace_memory`
|
|
17
|
+
- routes memory through enforced user/project scopes
|
|
18
|
+
- applies privacy filtering before writes
|
|
19
|
+
- logs to both OpenCode logs and a local file
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
flowchart TD
|
|
25
|
+
U[User message] --> OC[OpenCode turn]
|
|
26
|
+
OC --> EV[Plugin hooks]
|
|
27
|
+
EV --> RET[Hidden retrieval instruction]
|
|
28
|
+
EV --> AS[Autosave pending on idle/compaction]
|
|
29
|
+
RET --> LLM[Model]
|
|
30
|
+
AS --> LLM
|
|
31
|
+
LLM --> TOOL[mempalace_memory]
|
|
32
|
+
TOOL --> ADAPTER[Python adapter]
|
|
33
|
+
ADAPTER --> MP[MemPalace backend]
|
|
34
|
+
MP --> ADAPTER
|
|
35
|
+
ADAPTER --> TOOL
|
|
36
|
+
TOOL --> LLM
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Add to `opencode.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"plugin": ["@rvboris/opencode-mempalace"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
OpenCode installs plugin dependencies automatically.
|
|
50
|
+
|
|
51
|
+
## Local development
|
|
52
|
+
|
|
53
|
+
For source loading:
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
{
|
|
57
|
+
"$schema": "https://opencode.ai/config.json",
|
|
58
|
+
"plugin": [
|
|
59
|
+
"file:///ABSOLUTE/PATH/TO/mempalace-autosave/plugin/index.ts"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The plugin itself does not require the MemPalace MCP server. It uses the bundled Python adapter.
|
|
65
|
+
|
|
66
|
+
## Prerequisites
|
|
67
|
+
|
|
68
|
+
- Python 3.9+
|
|
69
|
+
- MemPalace installed and initialized
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install mempalace
|
|
73
|
+
mempalace init <dir>
|
|
74
|
+
mempalace mine <dir>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Build
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run build
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This builds TypeScript into `dist/` and copies the adapter to `dist/bridge/`.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Environment variables:
|
|
88
|
+
|
|
89
|
+
- `MEMPALACE_AUTOSAVE_ENABLED`
|
|
90
|
+
- `MEMPALACE_RETRIEVAL_ENABLED`
|
|
91
|
+
- `MEMPALACE_KEYWORD_SAVE_ENABLED`
|
|
92
|
+
- `MEMPALACE_PRIVACY_REDACTION_ENABLED`
|
|
93
|
+
- `MEMPALACE_MAX_INJECTED_ITEMS`
|
|
94
|
+
- `MEMPALACE_RETRIEVAL_QUERY_LIMIT`
|
|
95
|
+
- `MEMPALACE_AUTOSAVE_LOG_FILE`
|
|
96
|
+
- `MEMPALACE_ADAPTER_PYTHON`
|
|
97
|
+
- `MEMPALACE_USER_WING_PREFIX`
|
|
98
|
+
- `MEMPALACE_PROJECT_WING_PREFIX`
|
|
99
|
+
|
|
100
|
+
Optional config file: `~/.config/opencode/mempalace.jsonc`
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"autosaveEnabled": true,
|
|
105
|
+
"retrievalEnabled": true,
|
|
106
|
+
"keywordSaveEnabled": true,
|
|
107
|
+
"maxInjectedItems": 6,
|
|
108
|
+
"retrievalQueryLimit": 5,
|
|
109
|
+
"keywordPatterns": ["remember", "save this", "don't forget", "note that"],
|
|
110
|
+
"privacyRedactionEnabled": true,
|
|
111
|
+
"userWingPrefix": "wing_user",
|
|
112
|
+
"projectWingPrefix": "wing_project"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Runtime behavior
|
|
117
|
+
|
|
118
|
+
### Retrieval
|
|
119
|
+
|
|
120
|
+
On normal user turns the plugin can inject hidden retrieval guidance so the model searches existing memory before answering.
|
|
121
|
+
|
|
122
|
+
### Autosave
|
|
123
|
+
|
|
124
|
+
On `session.idle` and `session.compacted`, the plugin marks autosave as pending. The actual write happens on the next model turn through `mempalace_memory`.
|
|
125
|
+
|
|
126
|
+
### Safe wrapper tool
|
|
127
|
+
|
|
128
|
+
The model should use only:
|
|
129
|
+
|
|
130
|
+
- `mempalace_memory`
|
|
131
|
+
|
|
132
|
+
Direct mutation tools are blocked:
|
|
133
|
+
|
|
134
|
+
- `mempalace_add_drawer`
|
|
135
|
+
- `mempalace_kg_add`
|
|
136
|
+
- `mempalace_diary_write`
|
|
137
|
+
- matching `mcp-router_*` variants
|
|
138
|
+
|
|
139
|
+
## Scope policy
|
|
140
|
+
|
|
141
|
+
### User scope
|
|
142
|
+
|
|
143
|
+
- wing: `${MEMPALACE_USER_WING_PREFIX}_profile`
|
|
144
|
+
- rooms: `preferences`, `workflow`, `communication`
|
|
145
|
+
|
|
146
|
+
Use for:
|
|
147
|
+
- response preferences
|
|
148
|
+
- personal workflow habits
|
|
149
|
+
- cross-project conventions
|
|
150
|
+
|
|
151
|
+
### Project scope
|
|
152
|
+
|
|
153
|
+
- wing: `${MEMPALACE_PROJECT_WING_PREFIX}_${slug(projectName)}`
|
|
154
|
+
- rooms: `architecture`, `workflow`, `decisions`, `bugs`, `setup`
|
|
155
|
+
|
|
156
|
+
Use for:
|
|
157
|
+
- repo-specific setup
|
|
158
|
+
- architecture decisions
|
|
159
|
+
- build/test commands
|
|
160
|
+
- bug/solution patterns
|
|
161
|
+
|
|
162
|
+
## Examples
|
|
163
|
+
|
|
164
|
+
### Save a user preference
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
mempalace_memory
|
|
168
|
+
mode: save
|
|
169
|
+
scope: user
|
|
170
|
+
room: preferences
|
|
171
|
+
content: Prefers concise responses and numbered steps.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Save a project decision
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
mempalace_memory
|
|
178
|
+
mode: save
|
|
179
|
+
scope: project
|
|
180
|
+
room: decisions
|
|
181
|
+
content: Use Bun for builds and tests; avoid npm.
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Add KG fact
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
mempalace_memory
|
|
188
|
+
mode: kg_add
|
|
189
|
+
scope: project
|
|
190
|
+
subject: my-repo
|
|
191
|
+
predicate: uses
|
|
192
|
+
object: bun
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Search memory
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
mempalace_memory
|
|
199
|
+
mode: search
|
|
200
|
+
scope: user
|
|
201
|
+
room: preferences
|
|
202
|
+
query: user name
|
|
203
|
+
limit: 3
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Privacy
|
|
207
|
+
|
|
208
|
+
- supports `<private>...</private>` blocks
|
|
209
|
+
- redacts common secrets before writes
|
|
210
|
+
- refuses to save fully private content
|
|
211
|
+
|
|
212
|
+
## Logging
|
|
213
|
+
|
|
214
|
+
Logs are written to:
|
|
215
|
+
|
|
216
|
+
- OpenCode built-in logger
|
|
217
|
+
- file log: `~/.mempalace/opencode_autosave.log`
|
|
218
|
+
|
|
219
|
+
For debugging:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
opencode --log-level DEBUG
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Notes
|
|
226
|
+
|
|
227
|
+
- no visible autosave chat messages
|
|
228
|
+
- no OpenCode tool-to-tool calls
|
|
229
|
+
- adapter uses stdin/stdout streaming
|
|
230
|
+
- package is publishable as a standard OpenCode plugin
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Плагин MemPalace для OpenCode
|
|
2
|
+
|
|
3
|
+
> Постоянная память для OpenCode: скрытый retrieval, autosave и безопасный wrapper tool для MemPalace.
|
|
4
|
+
|
|
5
|
+
[English version](./README.md)
|
|
6
|
+
|
|
7
|
+
Плагин для [OpenCode](https://opencode.ai) со скрытым retrieval и autosave в [MemPalace](https://github.com/milla-jovovich/mempalace) через локальный Python adapter.
|
|
8
|
+
|
|
9
|
+
- OpenCode: https://opencode.ai
|
|
10
|
+
- MemPalace: https://github.com/milla-jovovich/mempalace
|
|
11
|
+
|
|
12
|
+
## Что делает
|
|
13
|
+
|
|
14
|
+
- добавляет скрытые подсказки для retrieval перед обычным ответом
|
|
15
|
+
- помечает autosave на событиях жизненного цикла сессии
|
|
16
|
+
- дает один безопасный tool: `mempalace_memory`
|
|
17
|
+
- направляет память в user/project scope по явным правилам
|
|
18
|
+
- применяет privacy-фильтрацию перед записью
|
|
19
|
+
- пишет логи в OpenCode и в локальный файл
|
|
20
|
+
|
|
21
|
+
## Схема работы
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
flowchart TD
|
|
25
|
+
U[Сообщение пользователя] --> OC[Ход OpenCode]
|
|
26
|
+
OC --> EV[Хуки плагина]
|
|
27
|
+
EV --> RET[Скрытая retrieval-инструкция]
|
|
28
|
+
EV --> AS[Autosave pending на idle/compaction]
|
|
29
|
+
RET --> LLM[Модель]
|
|
30
|
+
AS --> LLM
|
|
31
|
+
LLM --> TOOL[mempalace_memory]
|
|
32
|
+
TOOL --> ADAPTER[Python adapter]
|
|
33
|
+
ADAPTER --> MP[MemPalace backend]
|
|
34
|
+
MP --> ADAPTER
|
|
35
|
+
ADAPTER --> TOOL
|
|
36
|
+
TOOL --> LLM
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Установка
|
|
40
|
+
|
|
41
|
+
Добавь в `opencode.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"plugin": ["@rvboris/opencode-mempalace"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
OpenCode сам установит зависимости плагина при запуске.
|
|
50
|
+
|
|
51
|
+
## Локальная разработка
|
|
52
|
+
|
|
53
|
+
Для загрузки из исходников:
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
{
|
|
57
|
+
"$schema": "https://opencode.ai/config.json",
|
|
58
|
+
"plugin": [
|
|
59
|
+
"file:///ABSOLUTE/PATH/TO/mempalace-autosave/plugin/index.ts"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Самому плагину MemPalace MCP server не нужен — он использует встроенный Python adapter.
|
|
65
|
+
|
|
66
|
+
## Требования
|
|
67
|
+
|
|
68
|
+
- Python 3.9+
|
|
69
|
+
- установлен и инициализирован MemPalace
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install mempalace
|
|
73
|
+
mempalace init <dir>
|
|
74
|
+
mempalace mine <dir>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Сборка
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run build
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Команда компилирует TypeScript в `dist/` и копирует adapter в `dist/bridge/`.
|
|
84
|
+
|
|
85
|
+
## Конфигурация
|
|
86
|
+
|
|
87
|
+
Переменные окружения:
|
|
88
|
+
|
|
89
|
+
- `MEMPALACE_AUTOSAVE_ENABLED`
|
|
90
|
+
- `MEMPALACE_RETRIEVAL_ENABLED`
|
|
91
|
+
- `MEMPALACE_KEYWORD_SAVE_ENABLED`
|
|
92
|
+
- `MEMPALACE_PRIVACY_REDACTION_ENABLED`
|
|
93
|
+
- `MEMPALACE_MAX_INJECTED_ITEMS`
|
|
94
|
+
- `MEMPALACE_RETRIEVAL_QUERY_LIMIT`
|
|
95
|
+
- `MEMPALACE_AUTOSAVE_LOG_FILE`
|
|
96
|
+
- `MEMPALACE_ADAPTER_PYTHON`
|
|
97
|
+
- `MEMPALACE_USER_WING_PREFIX`
|
|
98
|
+
- `MEMPALACE_PROJECT_WING_PREFIX`
|
|
99
|
+
|
|
100
|
+
Дополнительно можно использовать файл `~/.config/opencode/mempalace.jsonc`:
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"autosaveEnabled": true,
|
|
105
|
+
"retrievalEnabled": true,
|
|
106
|
+
"keywordSaveEnabled": true,
|
|
107
|
+
"maxInjectedItems": 6,
|
|
108
|
+
"retrievalQueryLimit": 5,
|
|
109
|
+
"keywordPatterns": ["remember", "save this", "don't forget", "note that"],
|
|
110
|
+
"privacyRedactionEnabled": true,
|
|
111
|
+
"userWingPrefix": "wing_user",
|
|
112
|
+
"projectWingPrefix": "wing_project"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Как работает во время сессии
|
|
117
|
+
|
|
118
|
+
### Retrieval
|
|
119
|
+
|
|
120
|
+
На обычных пользовательских ходах плагин может подмешивать скрытую retrieval-инструкцию, чтобы модель сначала поискала релевантную память.
|
|
121
|
+
|
|
122
|
+
### Autosave
|
|
123
|
+
|
|
124
|
+
На `session.idle` и `session.compacted` плагин ставит autosave в состояние `pending`. Реальная запись делается на следующем ходе модели через `mempalace_memory`.
|
|
125
|
+
|
|
126
|
+
### Безопасный wrapper tool
|
|
127
|
+
|
|
128
|
+
Модель должна использовать только:
|
|
129
|
+
|
|
130
|
+
- `mempalace_memory`
|
|
131
|
+
|
|
132
|
+
Прямые mutating tools блокируются:
|
|
133
|
+
|
|
134
|
+
- `mempalace_add_drawer`
|
|
135
|
+
- `mempalace_kg_add`
|
|
136
|
+
- `mempalace_diary_write`
|
|
137
|
+
- соответствующие `mcp-router_*` варианты
|
|
138
|
+
|
|
139
|
+
## Политика scope
|
|
140
|
+
|
|
141
|
+
### User scope
|
|
142
|
+
|
|
143
|
+
- wing: `${MEMPALACE_USER_WING_PREFIX}_profile`
|
|
144
|
+
- rooms: `preferences`, `workflow`, `communication`
|
|
145
|
+
|
|
146
|
+
Используется для:
|
|
147
|
+
- предпочтений по ответам
|
|
148
|
+
- личных привычек работы
|
|
149
|
+
- кросс-проектных соглашений
|
|
150
|
+
|
|
151
|
+
### Project scope
|
|
152
|
+
|
|
153
|
+
- wing: `${MEMPALACE_PROJECT_WING_PREFIX}_${slug(projectName)}`
|
|
154
|
+
- rooms: `architecture`, `workflow`, `decisions`, `bugs`, `setup`
|
|
155
|
+
|
|
156
|
+
Используется для:
|
|
157
|
+
- repo-specific setup
|
|
158
|
+
- архитектурных решений
|
|
159
|
+
- build/test команд
|
|
160
|
+
- паттернов баг/решение
|
|
161
|
+
|
|
162
|
+
## Примеры
|
|
163
|
+
|
|
164
|
+
### Сохранить user preference
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
mempalace_memory
|
|
168
|
+
mode: save
|
|
169
|
+
scope: user
|
|
170
|
+
room: preferences
|
|
171
|
+
content: Prefers concise responses and numbered steps.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Сохранить project decision
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
mempalace_memory
|
|
178
|
+
mode: save
|
|
179
|
+
scope: project
|
|
180
|
+
room: decisions
|
|
181
|
+
content: Use Bun for builds and tests; avoid npm.
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Добавить KG факт
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
mempalace_memory
|
|
188
|
+
mode: kg_add
|
|
189
|
+
scope: project
|
|
190
|
+
subject: my-repo
|
|
191
|
+
predicate: uses
|
|
192
|
+
object: bun
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Поиск в памяти
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
mempalace_memory
|
|
199
|
+
mode: search
|
|
200
|
+
scope: user
|
|
201
|
+
room: preferences
|
|
202
|
+
query: user name
|
|
203
|
+
limit: 3
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Privacy
|
|
207
|
+
|
|
208
|
+
- поддерживаются блоки `<private>...</private>`
|
|
209
|
+
- common secrets редактируются перед записью
|
|
210
|
+
- полностью private content не сохраняется
|
|
211
|
+
|
|
212
|
+
## Логи
|
|
213
|
+
|
|
214
|
+
Логи пишутся в:
|
|
215
|
+
|
|
216
|
+
- встроенный лог OpenCode
|
|
217
|
+
- файл: `~/.mempalace/opencode_autosave.log`
|
|
218
|
+
|
|
219
|
+
Для отладки:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
opencode --log-level DEBUG
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Заметки
|
|
226
|
+
|
|
227
|
+
- никаких видимых autosave-сообщений в чате
|
|
228
|
+
- никаких OpenCode tool-to-tool вызовов
|
|
229
|
+
- adapter использует stdin/stdout streaming
|
|
230
|
+
- пакет подготовлен как полноценный publishable OpenCode plugin
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from mempalace.mcp_server import (
|
|
6
|
+
tool_add_drawer,
|
|
7
|
+
tool_diary_write,
|
|
8
|
+
tool_kg_add,
|
|
9
|
+
tool_search,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
payload = json.load(sys.stdin)
|
|
15
|
+
|
|
16
|
+
mode = payload["mode"]
|
|
17
|
+
if mode == "search":
|
|
18
|
+
result = tool_search(
|
|
19
|
+
query=payload["query"],
|
|
20
|
+
limit=payload.get("limit", 5),
|
|
21
|
+
wing=payload.get("wing"),
|
|
22
|
+
room=payload.get("room"),
|
|
23
|
+
)
|
|
24
|
+
elif mode == "save":
|
|
25
|
+
result = tool_add_drawer(
|
|
26
|
+
wing=payload["wing"],
|
|
27
|
+
room=payload["room"],
|
|
28
|
+
content=payload["content"],
|
|
29
|
+
added_by=payload.get("added_by", "opencode"),
|
|
30
|
+
)
|
|
31
|
+
elif mode == "kg_add":
|
|
32
|
+
result = tool_kg_add(
|
|
33
|
+
subject=payload["subject"],
|
|
34
|
+
predicate=payload["predicate"],
|
|
35
|
+
object=payload["object"],
|
|
36
|
+
valid_from=payload.get("valid_from", ""),
|
|
37
|
+
source_closet=payload.get("source_closet", ""),
|
|
38
|
+
)
|
|
39
|
+
elif mode == "diary_write":
|
|
40
|
+
result = tool_diary_write(
|
|
41
|
+
agent_name=payload["agent_name"],
|
|
42
|
+
entry=payload["entry"],
|
|
43
|
+
topic=payload.get("topic", "autosave"),
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
result = {"success": False, "error": f"Unknown mode: {mode}"}
|
|
47
|
+
|
|
48
|
+
output = json.dumps(result, ensure_ascii=False)
|
|
49
|
+
sys.stdout.buffer.write(output.encode("utf-8", errors="replace"))
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type PluginContext = {
|
|
2
|
+
client: any;
|
|
3
|
+
project: any;
|
|
4
|
+
directory: string;
|
|
5
|
+
worktree: string;
|
|
6
|
+
$: any;
|
|
7
|
+
};
|
|
8
|
+
export declare const eventHooks: (ctx: PluginContext) => {
|
|
9
|
+
event: ({ event }: {
|
|
10
|
+
event: any;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
"experimental.session.compacting": (input: {
|
|
13
|
+
sessionID?: string;
|
|
14
|
+
}, output: {
|
|
15
|
+
context: string[];
|
|
16
|
+
prompt?: string;
|
|
17
|
+
}) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AutosaveReason, AutosaveStatus, buildTranscriptDigest, buildUserDigest, extractLastUserMessage, finalizeAutosave, getSessionState, markKeywordSavePending, markFailed, markPending, markRetrievalPending, shouldScheduleAutosave, } from "../lib/autosave";
|
|
2
|
+
import { loadConfig } from "../lib/config";
|
|
3
|
+
import { writeLog } from "../lib/log";
|
|
4
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
const toReason = (eventType) => {
|
|
6
|
+
if (eventType === "session.compacted")
|
|
7
|
+
return AutosaveReason.Compacted;
|
|
8
|
+
if (eventType === "session.error")
|
|
9
|
+
return AutosaveReason.Error;
|
|
10
|
+
return AutosaveReason.Idle;
|
|
11
|
+
};
|
|
12
|
+
const loadMessages = async (ctx, sessionId) => {
|
|
13
|
+
const response = await ctx.client.session.messages({ path: { id: sessionId } });
|
|
14
|
+
return response?.data ?? response ?? [];
|
|
15
|
+
};
|
|
16
|
+
export const eventHooks = (ctx) => {
|
|
17
|
+
return {
|
|
18
|
+
event: async ({ event }) => {
|
|
19
|
+
try {
|
|
20
|
+
if (!["session.idle", "session.compacted", "session.error", "session.updated", "message.updated"].includes(event?.type))
|
|
21
|
+
return;
|
|
22
|
+
const sessionId = event?.properties?.sessionID;
|
|
23
|
+
if (!sessionId) {
|
|
24
|
+
await writeLog("WARN", "autosave event missing sessionID", { eventType: event?.type });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
if (event?.type === "session.updated" || event?.type === "message.updated") {
|
|
29
|
+
const messages = await loadMessages(ctx, sessionId);
|
|
30
|
+
const userDigest = buildUserDigest(messages);
|
|
31
|
+
if (config.retrievalEnabled) {
|
|
32
|
+
markRetrievalPending(sessionId, userDigest);
|
|
33
|
+
}
|
|
34
|
+
const lastUserMessage = extractLastUserMessage(messages);
|
|
35
|
+
if (lastUserMessage && config.keywordSaveEnabled && config.keywordPatterns.length) {
|
|
36
|
+
const keywordPattern = new RegExp(`\\b(${config.keywordPatterns.map(escapeRegex).join("|")})\\b`, "i");
|
|
37
|
+
if (config.keywordSaveEnabled && keywordPattern.test(lastUserMessage)) {
|
|
38
|
+
markKeywordSavePending(sessionId);
|
|
39
|
+
await writeLog("INFO", "keyword-triggered autosave hint detected", { sessionId });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!["session.idle", "session.compacted", "session.error"].includes(event?.type))
|
|
44
|
+
return;
|
|
45
|
+
await writeLog("INFO", "autosave trigger received", {
|
|
46
|
+
eventType: event?.type,
|
|
47
|
+
sessionId,
|
|
48
|
+
});
|
|
49
|
+
const state = getSessionState(sessionId);
|
|
50
|
+
if (state.status === AutosaveStatus.Running && event?.type === "session.idle") {
|
|
51
|
+
const finalized = finalizeAutosave(sessionId);
|
|
52
|
+
await writeLog("INFO", "finalized autosave turn", {
|
|
53
|
+
sessionId,
|
|
54
|
+
status: finalized.status,
|
|
55
|
+
toolCalls: finalized.successfulToolCalls.length,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (event?.type === "session.error") {
|
|
59
|
+
const failed = markFailed(sessionId);
|
|
60
|
+
await writeLog("ERROR", "autosave failed on session error", {
|
|
61
|
+
sessionId,
|
|
62
|
+
retryCount: failed.retryCount,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!config.autosaveEnabled)
|
|
67
|
+
return;
|
|
68
|
+
const messages = await loadMessages(ctx, sessionId);
|
|
69
|
+
const userDigest = buildUserDigest(messages);
|
|
70
|
+
const transcriptDigest = buildTranscriptDigest(messages);
|
|
71
|
+
if (!shouldScheduleAutosave(sessionId, userDigest, transcriptDigest)) {
|
|
72
|
+
await writeLog("INFO", "skipping autosave state", {
|
|
73
|
+
sessionId,
|
|
74
|
+
reason: toReason(event?.type),
|
|
75
|
+
userDigest,
|
|
76
|
+
transcriptDigest,
|
|
77
|
+
status: getSessionState(sessionId).status,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
markPending(sessionId, toReason(event?.type), userDigest, transcriptDigest);
|
|
82
|
+
await writeLog("INFO", "marked autosave pending", {
|
|
83
|
+
sessionId,
|
|
84
|
+
reason: toReason(event?.type),
|
|
85
|
+
userDigest,
|
|
86
|
+
transcriptDigest,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
await writeLog("ERROR", "event hook failed", {
|
|
91
|
+
error: error instanceof Error ? error.message : String(error),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"experimental.session.compacting": async (input, output) => {
|
|
96
|
+
if (!input.sessionID) {
|
|
97
|
+
await writeLog("WARN", "compaction hook missing sessionID", {});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const state = getSessionState(input.sessionID);
|
|
101
|
+
if (state.status !== AutosaveStatus.Pending)
|
|
102
|
+
return;
|
|
103
|
+
output.context.push("MemPalace autosave is pending. Before answering after compaction, persist durable facts, decisions, preferences, outcomes, and diary notes using MemPalace MCP tools. Do not dump the full transcript.");
|
|
104
|
+
await writeLog("INFO", "injected compaction autosave context", {
|
|
105
|
+
sessionId: input.sessionID,
|
|
106
|
+
reason: state.pendingReason,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
};
|