n2-soul 4.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.
@@ -0,0 +1,246 @@
1
+ // Soul KV-Cache — MCP tool registration. Exposes KV-Cache to agents.
2
+ const { SoulKVCache } = require('../lib/kv-cache');
3
+
4
+ /**
5
+ * Registers KV-Cache MCP tools.
6
+ * @param {object} server - MCP server
7
+ * @param {object} z - Zod validator
8
+ * @param {object} config - Soul config
9
+ */
10
+ function registerKVCacheTools(server, z, config) {
11
+ if (!config.KV_CACHE?.enabled) return;
12
+
13
+ const kvCache = new SoulKVCache(config.DATA_DIR, config.KV_CACHE);
14
+
15
+ // n2_kv_save — Save session snapshot
16
+ server.registerTool(
17
+ 'n2_kv_save',
18
+ {
19
+ title: 'N2 KV Save',
20
+ description: 'Save current session as a KV-Cache snapshot. Auto-called at n2_work_end if enabled.',
21
+ inputSchema: {
22
+ agent: z.string().describe('Agent name'),
23
+ project: z.string().describe('Project name'),
24
+ summary: z.string().describe('Session summary'),
25
+ decisions: z.array(z.string()).optional().describe('Key decisions made'),
26
+ todo: z.array(z.string()).optional().describe('TODO items for next session'),
27
+ filesCreated: z.array(z.object({
28
+ path: z.string(), desc: z.string(),
29
+ })).optional().describe('Files created'),
30
+ filesModified: z.array(z.object({
31
+ path: z.string(), desc: z.string(),
32
+ })).optional().describe('Files modified'),
33
+ },
34
+ },
35
+ async ({ agent, project, summary, decisions, todo, filesCreated, filesModified }) => {
36
+ try {
37
+ const id = kvCache.save(agent, project, {
38
+ summary,
39
+ decisions: decisions || [],
40
+ todo: todo || [],
41
+ filesCreated: filesCreated || [],
42
+ filesModified: filesModified || [],
43
+ });
44
+ const snapCount = kvCache.listSnapshots(project).length;
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: `KV-Cache saved: ${id} (${snapCount} total for ${project})`,
49
+ }],
50
+ };
51
+ } catch (err) {
52
+ return { content: [{ type: 'text', text: `KV-Cache save error: ${err.message}` }] };
53
+ }
54
+ }
55
+ );
56
+
57
+ // n2_kv_load — Load most recent snapshot
58
+ server.registerTool(
59
+ 'n2_kv_load',
60
+ {
61
+ title: 'N2 KV Load',
62
+ description: 'Load the most recent KV-Cache snapshot for a project. Returns compressed context.',
63
+ inputSchema: {
64
+ project: z.string().describe('Project name'),
65
+ budget: z.number().optional().describe('Token budget for context (default: 2000)'),
66
+ level: z.string().optional().describe('Progressive level: L1 (minimal), L2 (standard), L3 (full), auto (default)'),
67
+ },
68
+ },
69
+ async ({ project, budget, level }) => {
70
+ try {
71
+ const snap = kvCache.load(project, { budget, level: level || 'auto' });
72
+ if (!snap) {
73
+ return { content: [{ type: 'text', text: `No KV-Cache snapshots found for ${project}.` }] };
74
+ }
75
+ const header = `[${snap._level || 'auto'} | ~${snap._promptTokens || '?'} tokens]`;
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: `${header}\n${snap._resumePrompt || `Snapshot ${snap.id} loaded.`}`,
80
+ }],
81
+ };
82
+ } catch (err) {
83
+ return { content: [{ type: 'text', text: `KV-Cache load error: ${err.message}` }] };
84
+ }
85
+ }
86
+ );
87
+
88
+ // n2_kv_search — Search across snapshots
89
+ server.registerTool(
90
+ 'n2_kv_search',
91
+ {
92
+ title: 'N2 KV Search',
93
+ description: 'Search across KV-Cache snapshots for relevant past sessions.',
94
+ inputSchema: {
95
+ query: z.string().describe('Search query (keywords, space-separated)'),
96
+ project: z.string().describe('Project name'),
97
+ maxResults: z.number().optional().describe('Max results (default: 5)'),
98
+ },
99
+ },
100
+ async ({ query, project, maxResults }) => {
101
+ try {
102
+ const results = await kvCache.search(query, project, maxResults || 5);
103
+ if (results.length === 0) {
104
+ return { content: [{ type: 'text', text: `No KV-Cache results for "${query}" in ${project}.` }] };
105
+ }
106
+
107
+ const lines = results.map((r, i) => {
108
+ const date = (r.endedAt || r.startedAt || '').split('T')[0];
109
+ return `${i + 1}. [${date}] ${r.agentName} | ${r.keys.slice(0, 5).join(', ')}\n ${(r.context?.summary || '').slice(0, 150)}`;
110
+ });
111
+
112
+ return {
113
+ content: [{
114
+ type: 'text',
115
+ text: `KV-Cache search: "${query}" (${results.length} results)\n\n${lines.join('\n\n')}`,
116
+ }],
117
+ };
118
+ } catch (err) {
119
+ return { content: [{ type: 'text', text: `KV-Cache search error: ${err.message}` }] };
120
+ }
121
+ }
122
+ );
123
+
124
+ // n2_kv_gc — Garbage collect old snapshots
125
+ server.registerTool(
126
+ 'n2_kv_gc',
127
+ {
128
+ title: 'N2 KV Garbage Collect',
129
+ description: 'Remove old KV-Cache snapshots. Uses config defaults if no args.',
130
+ inputSchema: {
131
+ project: z.string().describe('Project name'),
132
+ maxAgeDays: z.number().optional().describe('Delete older than N days'),
133
+ },
134
+ },
135
+ async ({ project, maxAgeDays }) => {
136
+ try {
137
+ const result = kvCache.gc(project, maxAgeDays);
138
+ const remaining = kvCache.listSnapshots(project).length;
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: `KV-Cache GC: ${result.deleted} deleted, ${remaining} remaining for ${project}.`,
143
+ }],
144
+ };
145
+ } catch (err) {
146
+ return { content: [{ type: 'text', text: `KV-Cache GC error: ${err.message}` }] };
147
+ }
148
+ }
149
+ );
150
+
151
+ // n2_kv_backup — Backup project data
152
+ server.registerTool(
153
+ 'n2_kv_backup',
154
+ {
155
+ title: 'N2 KV Backup',
156
+ description: 'Backup KV-Cache data to a portable SQLite DB. Supports incremental backups.',
157
+ inputSchema: {
158
+ project: z.string().describe('Project name'),
159
+ full: z.boolean().optional().describe('Force full backup (ignore incremental)'),
160
+ },
161
+ },
162
+ async ({ project, full }) => {
163
+ try {
164
+ const result = await kvCache.backup(project, { full });
165
+ if (result.type === 'skip') {
166
+ return { content: [{ type: 'text', text: `KV-Cache backup skipped: ${result.message}` }] };
167
+ }
168
+ if (result.type === 'empty') {
169
+ return { content: [{ type: 'text', text: `KV-Cache backup: no data for ${project}.` }] };
170
+ }
171
+ return {
172
+ content: [{
173
+ type: 'text',
174
+ text: `KV-Cache backup created: ${result.backupId}\nType: ${result.type} | Size: ${result.sizeFormatted}\nSnapshots: ${result.snapshots || 'copied'} | Embeddings: ${result.embeddings || 'included'}`,
175
+ }],
176
+ };
177
+ } catch (err) {
178
+ return { content: [{ type: 'text', text: `KV-Cache backup error: ${err.message}` }] };
179
+ }
180
+ }
181
+ );
182
+
183
+ // n2_kv_restore — Restore from backup
184
+ server.registerTool(
185
+ 'n2_kv_restore',
186
+ {
187
+ title: 'N2 KV Restore',
188
+ description: 'Restore KV-Cache data from a backup DB.',
189
+ inputSchema: {
190
+ project: z.string().describe('Project name'),
191
+ backupId: z.string().optional().describe('Backup ID (default: latest)'),
192
+ target: z.string().optional().describe('Restore target: json (default) or sqlite'),
193
+ },
194
+ },
195
+ async ({ project, backupId, target }) => {
196
+ try {
197
+ const result = await kvCache.restore(project, backupId, { target });
198
+ if (result.error) {
199
+ return { content: [{ type: 'text', text: `KV-Cache restore error: ${result.error}` }] };
200
+ }
201
+ return {
202
+ content: [{
203
+ type: 'text',
204
+ text: `KV-Cache restored from ${result.backupId}: ${result.restored} snapshots, ${result.embeddings || 0} embeddings (target: ${result.target})`,
205
+ }],
206
+ };
207
+ } catch (err) {
208
+ return { content: [{ type: 'text', text: `KV-Cache restore error: ${err.message}` }] };
209
+ }
210
+ }
211
+ );
212
+
213
+ // n2_kv_backup_list — List backup history
214
+ server.registerTool(
215
+ 'n2_kv_backup_list',
216
+ {
217
+ title: 'N2 KV Backup List',
218
+ description: 'List KV-Cache backup history for a project.',
219
+ inputSchema: {
220
+ project: z.string().describe('Project name'),
221
+ },
222
+ },
223
+ async ({ project }) => {
224
+ try {
225
+ const backups = kvCache.listBackups(project);
226
+ if (backups.length === 0) {
227
+ return { content: [{ type: 'text', text: `No backups for ${project}.` }] };
228
+ }
229
+ const status = kvCache.backupStatus(project);
230
+ const lines = backups.map((b, i) =>
231
+ `${i + 1}. [${b.id}] ${b.type} | ${b.sizeFormatted} | ${b.timestamp?.split('T')[0]}`
232
+ );
233
+ return {
234
+ content: [{
235
+ type: 'text',
236
+ text: `KV-Cache backups for ${project}: ${status.totalBackups} total (${status.totalBackupSize})\nLast: ${status.lastBackup || 'never'}\n\n${lines.join('\n')}`,
237
+ }],
238
+ };
239
+ } catch (err) {
240
+ return { content: [{ type: 'text', text: `KV-Cache backup list error: ${err.message}` }] };
241
+ }
242
+ }
243
+ );
244
+ }
245
+
246
+ module.exports = { registerKVCacheTools };