@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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/README.ru.md +230 -0
  4. package/dist/bridge/mempalace_adapter.py +54 -0
  5. package/dist/plugin/hooks/chat-params.d.ts +5 -0
  6. package/dist/plugin/hooks/chat-params.js +8 -0
  7. package/dist/plugin/hooks/event.d.ts +19 -0
  8. package/dist/plugin/hooks/event.js +110 -0
  9. package/dist/plugin/hooks/system.d.ts +10 -0
  10. package/dist/plugin/hooks/system.js +50 -0
  11. package/dist/plugin/hooks/tool.d.ts +17 -0
  12. package/dist/plugin/hooks/tool.js +61 -0
  13. package/dist/plugin/index.d.ts +2 -0
  14. package/dist/plugin/index.js +18 -0
  15. package/dist/plugin/lib/adapter.d.ts +1 -0
  16. package/dist/plugin/lib/adapter.js +42 -0
  17. package/dist/plugin/lib/autosave.d.ts +52 -0
  18. package/dist/plugin/lib/autosave.js +242 -0
  19. package/dist/plugin/lib/config.d.ts +13 -0
  20. package/dist/plugin/lib/config.js +53 -0
  21. package/dist/plugin/lib/context.d.ts +12 -0
  22. package/dist/plugin/lib/context.js +37 -0
  23. package/dist/plugin/lib/derive.d.ts +1 -0
  24. package/dist/plugin/lib/derive.js +36 -0
  25. package/dist/plugin/lib/enforcement.d.ts +1 -0
  26. package/dist/plugin/lib/enforcement.js +10 -0
  27. package/dist/plugin/lib/log.d.ts +11 -0
  28. package/dist/plugin/lib/log.js +48 -0
  29. package/dist/plugin/lib/privacy.d.ts +3 -0
  30. package/dist/plugin/lib/privacy.js +19 -0
  31. package/dist/plugin/lib/scope.d.ts +8 -0
  32. package/dist/plugin/lib/scope.js +16 -0
  33. package/dist/plugin/tools/mempalace-memory.d.ts +42 -0
  34. package/dist/plugin/tools/mempalace-memory.js +89 -0
  35. package/example-opencode.json +4 -0
  36. 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,5 @@
1
+ export declare const chatParamHooks: () => {
2
+ "chat.params": (input: {
3
+ sessionID: string;
4
+ }) => Promise<void>;
5
+ };
@@ -0,0 +1,8 @@
1
+ import { setCurrentTurnSessionId } from "../lib/autosave";
2
+ export const chatParamHooks = () => {
3
+ return {
4
+ "chat.params": async (input) => {
5
+ setCurrentTurnSessionId(input.sessionID);
6
+ },
7
+ };
8
+ };
@@ -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
+ };
@@ -0,0 +1,10 @@
1
+ type PluginContext = {
2
+ client: any;
3
+ project: any;
4
+ };
5
+ export declare const systemHooks: (ctx: PluginContext) => {
6
+ "experimental.chat.system.transform": (_input: {}, output: {
7
+ system: string[];
8
+ }) => Promise<void>;
9
+ };
10
+ export {};