squish-memory 0.7.0 → 0.7.2
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/dist/index.d.ts +8 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +385 -687
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/squish-memory/SKILL.md +18 -13
package/dist/index.js
CHANGED
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Squish v0.
|
|
3
|
+
* Squish v0.7.0 - Dual-Mode CLI + MCP Server
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
|
|
5
|
+
* Modes:
|
|
6
|
+
* - CLI Mode: For OpenClaw bash execution (e.g., `squish remember "text"`)
|
|
7
|
+
* - MCP Mode: For Claude Code (default, no args)
|
|
7
8
|
*
|
|
8
9
|
* Features:
|
|
9
|
-
* - 11 consolidated MCP tools
|
|
10
|
-
* - Local mode: SQLite with FTS5
|
|
11
|
-
* - Team mode: PostgreSQL + pgvector
|
|
12
|
-
* -
|
|
13
|
-
* - Privacy-first: Secret detection, <private> tag filtering, async worker pipeline
|
|
14
|
-
* - Pluggable embeddings: OpenAI, Ollama, or local TF-IDF
|
|
15
|
-
*
|
|
16
|
-
* Consolidated MCP Tools (v0.6.0):
|
|
17
|
-
* - core_memory: Unified tool for view/edit/append operations (was 3 tools)
|
|
18
|
-
* - context_paging: Unified tool for load/evict/view operations (was 3 tools)
|
|
19
|
-
* - merge: Unified tool for detect/list/preview/stats/approve/reject/reverse (was 2 tools)
|
|
20
|
-
* - context_status, remember, recall, search, observe, context, health (unchanged)
|
|
21
|
-
* - lifecycle, summarize_session, protect_memory (unchanged)
|
|
22
|
-
*
|
|
23
|
-
* Plugin hooks (registered in plugin.json):
|
|
24
|
-
* - onInstall: Initialize database, create config files
|
|
25
|
-
* - onSessionStart: Inject relevant context + generate folder context
|
|
26
|
-
* - onUserPromptSubmit: Auto-capture user prompts with privacy filtering
|
|
27
|
-
* - onPostToolUse: Auto-capture tool executions and observe patterns
|
|
28
|
-
* - onSessionStop: Finalize observations, summarize via async worker
|
|
29
|
-
*
|
|
30
|
-
* See src/plugin/plugin-wrapper.ts for hook implementations
|
|
31
|
-
* See docs/MIGRATION-v0.6.0.md for migration guide
|
|
10
|
+
* - 11 consolidated MCP tools
|
|
11
|
+
* - Local mode: SQLite with FTS5
|
|
12
|
+
* - Team mode: PostgreSQL + pgvector
|
|
13
|
+
* - OpenClaw CLI commands: remember, search, recall, core_memory
|
|
32
14
|
*/
|
|
33
15
|
import 'dotenv/config';
|
|
16
|
+
import { Command } from 'commander';
|
|
34
17
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
35
18
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
36
19
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -38,7 +21,6 @@ import { logger } from './core/logger.js';
|
|
|
38
21
|
import { checkDatabaseHealth, config } from './db/index.js';
|
|
39
22
|
import { checkRedisHealth, closeCache } from './core/cache.js';
|
|
40
23
|
import { rememberMemory, getMemoryById, searchMemories } from './core/memory/memories.js';
|
|
41
|
-
import { searchConversations, getRecentConversations } from './core/search/conversations.js';
|
|
42
24
|
import { createObservation } from './core/observations.js';
|
|
43
25
|
import { getProjectContext } from './core/context.js';
|
|
44
26
|
import { startWebServer } from './api/web/web.js';
|
|
@@ -49,587 +31,384 @@ import { handleApproveMerge } from './algorithms/merge/handlers/approve-merge.js
|
|
|
49
31
|
import { handleRejectMerge } from './algorithms/merge/handlers/reject-merge.js';
|
|
50
32
|
import { handleReverseMerge } from './algorithms/merge/handlers/reverse-merge.js';
|
|
51
33
|
import { handleGetMergeStats } from './algorithms/merge/handlers/get-stats.js';
|
|
52
|
-
import { forceLifecycleMaintenance } from './core/worker.js';
|
|
53
|
-
import { summarizeSession } from './core/summarization.js';
|
|
54
|
-
import { storeAgentMemory } from './core/agent-memory.js';
|
|
55
|
-
import { getRelatedMemories } from './core/associations.js';
|
|
56
|
-
import { protectMemory, pinMemory } from './core/governance.js';
|
|
57
|
-
import { isDatabaseUnavailableError, determineOverallStatus } from './core/utils.js';
|
|
58
34
|
import { searchWithQMD, isQMDAvailable } from './core/search/qmd-search.js';
|
|
59
35
|
import { initializeCoreMemory, getCoreMemory, editCoreMemorySection, appendCoreMemorySection, getCoreMemoryStats, } from './core/core-memory.js';
|
|
60
36
|
import { loadMemoryToContext, evictMemoryFromContext, viewLoadedMemories, getContextStatus, } from './core/context-paging.js';
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
37
|
+
import { ensureDataDirectory } from './db/bootstrap.js';
|
|
38
|
+
const VERSION = '0.7.0';
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// CLI MODE DETECTION
|
|
41
|
+
// ============================================================================
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
const hasCliArgs = args.length > 0 && args[0] !== '--mcp';
|
|
44
|
+
if (hasCliArgs) {
|
|
45
|
+
// === CLI MODE (for OpenClaw) ===
|
|
46
|
+
runCliMode().catch((e) => {
|
|
47
|
+
console.error(JSON.stringify({ error: e.message }, null, 2));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// === MCP MODE (for Claude Code) - DEFAULT ===
|
|
53
|
+
runMcpMode().catch((e) => {
|
|
54
|
+
logger.error('Fatal error', e);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// CLI MODE (for OpenClaw bash execution)
|
|
60
|
+
// ============================================================================
|
|
61
|
+
async function runCliMode() {
|
|
62
|
+
const program = new Command();
|
|
63
|
+
program
|
|
64
|
+
.name('squish')
|
|
65
|
+
.description('Squish - Persistent memory for AI assistants')
|
|
66
|
+
.version(VERSION);
|
|
67
|
+
// Initialize data directory before any command
|
|
68
|
+
program.hook('preAction', async () => {
|
|
69
|
+
await ensureDataDirectory();
|
|
70
|
+
});
|
|
71
|
+
// squish remember "content" --type fact --tags tag1,tag2
|
|
72
|
+
program
|
|
73
|
+
.command('remember <content>')
|
|
74
|
+
.description('Store a memory')
|
|
75
|
+
.option('-t, --type <type>', 'Memory type (observation, fact, decision, context, preference)', 'observation')
|
|
76
|
+
.option('-T, --tags <tags>', 'Comma-separated tags', '')
|
|
77
|
+
.option('-p, --project <project>', 'Project path', process.cwd())
|
|
78
|
+
.action(async (content, options) => {
|
|
79
|
+
try {
|
|
80
|
+
const result = await rememberMemory({
|
|
81
|
+
content,
|
|
82
|
+
type: options.type,
|
|
83
|
+
tags: options.tags ? options.tags.split(',').map((t) => t.trim()) : [],
|
|
84
|
+
project: options.project,
|
|
85
|
+
});
|
|
86
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// squish search "query" --type fact --limit 10
|
|
94
|
+
program
|
|
95
|
+
.command('search <query>')
|
|
96
|
+
.description('Search memories')
|
|
97
|
+
.option('-t, --type <type>', 'Filter by memory type')
|
|
98
|
+
.option('-l, --limit <number>', 'Max results', '10')
|
|
99
|
+
.option('-p, --project <project>', 'Project path', process.cwd())
|
|
100
|
+
.action(async (query, options) => {
|
|
101
|
+
try {
|
|
102
|
+
const results = await searchMemories({
|
|
103
|
+
query,
|
|
104
|
+
type: options.type,
|
|
105
|
+
limit: parseInt(options.limit, 10),
|
|
106
|
+
project: options.project,
|
|
107
|
+
});
|
|
108
|
+
console.log(JSON.stringify({ ok: true, query, count: results?.length || 0, results }, null, 2));
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
description: 'View comprehensive context window status and token usage',
|
|
113
|
-
inputSchema: {
|
|
114
|
-
type: 'object',
|
|
115
|
-
properties: {
|
|
116
|
-
sessionId: { type: 'string', description: 'Session ID' },
|
|
117
|
-
projectId: { type: 'string', description: 'Project ID' },
|
|
118
|
-
},
|
|
119
|
-
required: ['sessionId', 'projectId']
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
112
|
+
process.exit(1);
|
|
120
113
|
}
|
|
121
|
-
}
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
properties: {
|
|
131
|
-
content: { type: 'string', description: 'Content to store' },
|
|
132
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
133
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
134
|
-
project: { type: 'string', description: 'Project path' },
|
|
135
|
-
metadata: { type: 'object', description: 'Custom metadata' },
|
|
136
|
-
agentId: { type: 'string', description: 'Agent identifier (optional)' },
|
|
137
|
-
agentRole: { type: 'string', description: 'Agent role (optional)' },
|
|
138
|
-
visibilityScope: { type: 'string', enum: ['private', 'project', 'team', 'global'], description: 'Visibility scope for agent memories' },
|
|
139
|
-
sector: { type: 'string', enum: ['episodic', 'semantic', 'procedural', 'autobiographical', 'working'], description: 'Memory sector classification' }
|
|
140
|
-
},
|
|
141
|
-
required: ['content']
|
|
114
|
+
});
|
|
115
|
+
// squish recall <memoryId>
|
|
116
|
+
program
|
|
117
|
+
.command('recall <memoryId>')
|
|
118
|
+
.description('Retrieve a memory by ID')
|
|
119
|
+
.action(async (memoryId) => {
|
|
120
|
+
try {
|
|
121
|
+
const memory = await getMemoryById(String(memoryId));
|
|
122
|
+
console.log(JSON.stringify({ ok: true, found: !!memory, memory }, null, 2));
|
|
142
123
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
description: 'Retrieve a specific stored memory by ID (like reading a file). Use this when you have a memory ID and want to get its full content.',
|
|
147
|
-
inputSchema: {
|
|
148
|
-
type: 'object',
|
|
149
|
-
properties: {
|
|
150
|
-
id: { type: 'string', description: 'Memory UUID' }
|
|
151
|
-
},
|
|
152
|
-
required: ['id']
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
126
|
+
process.exit(1);
|
|
153
127
|
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
128
|
+
});
|
|
129
|
+
// squish core_memory view
|
|
130
|
+
// squish core_memory edit persona --content "I am helpful"
|
|
131
|
+
// squish core_memory append user_info --text "Prefers TypeScript"
|
|
132
|
+
program
|
|
133
|
+
.command('core_memory')
|
|
134
|
+
.description('Manage core memory (always-visible context)')
|
|
135
|
+
.argument('[action]', 'view, edit, append', 'view')
|
|
136
|
+
.option('-s, --section <section>', 'Section: persona, user_info, project_context, working_notes')
|
|
137
|
+
.option('-c, --content <content>', 'New content (for edit)')
|
|
138
|
+
.option('-t, --text <text>', 'Text to append (for append)')
|
|
139
|
+
.option('-p, --project <project>', 'Project path', process.cwd())
|
|
140
|
+
.action(async (action, options) => {
|
|
141
|
+
try {
|
|
142
|
+
const project = options.project;
|
|
143
|
+
switch (action) {
|
|
144
|
+
case 'view':
|
|
145
|
+
await initializeCoreMemory(project);
|
|
146
|
+
const core = await getCoreMemory(project);
|
|
147
|
+
const stats = await getCoreMemoryStats(project);
|
|
148
|
+
console.log(JSON.stringify({ ok: true, action, content: core, stats }, null, 2));
|
|
149
|
+
break;
|
|
150
|
+
case 'edit':
|
|
151
|
+
if (!options.section || !options.content) {
|
|
152
|
+
console.log(JSON.stringify({ ok: false, error: '--section and --content required for edit' }, null, 2));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
await initializeCoreMemory(project);
|
|
156
|
+
const editResult = await editCoreMemorySection(project, options.section, String(options.content));
|
|
157
|
+
console.log(JSON.stringify({ ok: editResult.success, action: 'edit', section: options.section, ...editResult }, null, 2));
|
|
158
|
+
break;
|
|
159
|
+
case 'append':
|
|
160
|
+
if (!options.section || !options.text) {
|
|
161
|
+
console.log(JSON.stringify({ ok: false, error: '--section and --text required for append' }, null, 2));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
await initializeCoreMemory(project);
|
|
165
|
+
const appendResult = await appendCoreMemorySection(project, options.section, String(options.text));
|
|
166
|
+
console.log(JSON.stringify({ ok: appendResult.success, action: 'append', section: options.section, ...appendResult }, null, 2));
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
console.log(JSON.stringify({ ok: false, error: `Unknown action: ${action}` }, null, 2));
|
|
170
|
+
process.exit(1);
|
|
176
171
|
}
|
|
177
172
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
description: 'Record an observation about your work (tool usage, patterns, errors). Use this to document what you\'re learning about the codebase and problems you encounter.',
|
|
182
|
-
inputSchema: {
|
|
183
|
-
type: 'object',
|
|
184
|
-
properties: {
|
|
185
|
-
type: { type: 'string', enum: ['tool_use', 'file_change', 'error', 'pattern', 'insight'] },
|
|
186
|
-
action: { type: 'string', description: 'Action taken' },
|
|
187
|
-
target: { type: 'string', description: 'Target of action' },
|
|
188
|
-
summary: { type: 'string', description: 'Summary' },
|
|
189
|
-
details: { type: 'object' },
|
|
190
|
-
session: { type: 'string', description: 'Session ID' }
|
|
191
|
-
},
|
|
192
|
-
required: ['type', 'action', 'summary']
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
175
|
+
process.exit(1);
|
|
193
176
|
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
177
|
+
});
|
|
178
|
+
await program.parseAsync(process.argv);
|
|
179
|
+
}
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// MCP MODE (for Claude Code) - DEFAULT
|
|
182
|
+
// ============================================================================
|
|
183
|
+
async function runMcpMode() {
|
|
184
|
+
const TOOLS = [
|
|
185
|
+
// Core Memory Tool
|
|
186
|
+
{
|
|
187
|
+
name: 'core_memory',
|
|
188
|
+
description: 'View or edit your core memory (always-visible). Use this to see your persona, user info, project context, and working notes.',
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
action: { type: 'string', enum: ['view', 'edit', 'append'] },
|
|
193
|
+
projectId: { type: 'string' },
|
|
194
|
+
section: { type: 'string', enum: ['persona', 'user_info', 'project_context', 'working_notes'] },
|
|
195
|
+
content: { type: 'string' },
|
|
196
|
+
text: { type: 'string' },
|
|
206
197
|
},
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
type: 'string',
|
|
220
|
-
|
|
198
|
+
required: ['action', 'projectId']
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
// Context Paging
|
|
202
|
+
{
|
|
203
|
+
name: 'context_paging',
|
|
204
|
+
description: 'Manage your working memory set. Load, evict, or view loaded memories.',
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
action: { type: 'string', enum: ['load', 'evict', 'view'] },
|
|
209
|
+
sessionId: { type: 'string' },
|
|
210
|
+
memoryId: { type: 'string' },
|
|
211
|
+
},
|
|
212
|
+
required: ['action', 'sessionId']
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'context_status',
|
|
217
|
+
description: 'View comprehensive context window status and token usage',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
sessionId: { type: 'string' },
|
|
222
|
+
projectId: { type: 'string' },
|
|
223
|
+
},
|
|
224
|
+
required: ['sessionId', 'projectId']
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
// Memory Tools
|
|
228
|
+
{
|
|
229
|
+
name: 'remember',
|
|
230
|
+
description: 'Store information for future use. Perfect for facts, decisions, code snippets, configuration details, or user preferences.',
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: {
|
|
234
|
+
content: { type: 'string' },
|
|
235
|
+
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
236
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
237
|
+
project: { type: 'string' },
|
|
238
|
+
metadata: { type: 'object' },
|
|
239
|
+
},
|
|
240
|
+
required: ['content']
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'recall',
|
|
245
|
+
description: 'Retrieve a specific stored memory by ID',
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: 'object',
|
|
248
|
+
properties: { id: { type: 'string' } },
|
|
249
|
+
required: ['id']
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'search',
|
|
254
|
+
description: 'Search your stored memories. Leave query empty to list recent memories.',
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {
|
|
258
|
+
query: { type: 'string' },
|
|
259
|
+
scope: { type: 'string', enum: ['memories', 'conversations', 'recent'], default: 'memories' },
|
|
260
|
+
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
261
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
262
|
+
limit: { type: 'number', default: 10 },
|
|
263
|
+
project: { type: 'string' },
|
|
221
264
|
}
|
|
222
265
|
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
properties: {
|
|
236
|
-
action: {
|
|
237
|
-
type: 'string',
|
|
238
|
-
enum: ['detect', 'list', 'preview', 'stats', 'approve', 'reject', 'reverse'],
|
|
239
|
-
description: 'Merge action: detect duplicates, list proposals, preview merge, view stats, approve, reject, or reverse'
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'observe',
|
|
269
|
+
description: 'Record an observation about your work (tool usage, patterns, errors)',
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
type: { type: 'string', enum: ['tool_use', 'file_change', 'error', 'pattern', 'insight'] },
|
|
274
|
+
action: { type: 'string' },
|
|
275
|
+
target: { type: 'string' },
|
|
276
|
+
summary: { type: 'string' },
|
|
277
|
+
details: { type: 'object' },
|
|
240
278
|
},
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
279
|
+
required: ['type', 'action', 'summary']
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'context',
|
|
284
|
+
description: 'Get project context',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
project: { type: 'string' },
|
|
289
|
+
include: { type: 'array', items: { type: 'string' }, default: ['memories', 'observations'] },
|
|
290
|
+
limit: { type: 'number', default: 10 }
|
|
291
|
+
},
|
|
292
|
+
required: ['project']
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: 'init',
|
|
297
|
+
description: 'Initialize Squish memory system for the current project',
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: { projectPath: { type: 'string' } }
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'health',
|
|
305
|
+
description: 'Check service status',
|
|
306
|
+
inputSchema: { type: 'object', properties: {} }
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'merge',
|
|
310
|
+
description: 'Manage memory merges: detect, list, preview, approve, reject, reverse',
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
action: { type: 'string', enum: ['detect', 'list', 'preview', 'stats', 'approve', 'reject', 'reverse'] },
|
|
315
|
+
projectId: { type: 'string' },
|
|
316
|
+
proposalId: { type: 'string' },
|
|
317
|
+
threshold: { type: 'number' },
|
|
318
|
+
},
|
|
319
|
+
required: ['action']
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'qmd_search',
|
|
324
|
+
description: 'Search memories using QMD hybrid search (BM25 + vector + rerank)',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
query: { type: 'string' },
|
|
329
|
+
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'] },
|
|
330
|
+
limit: { type: 'number', default: 10 },
|
|
331
|
+
},
|
|
332
|
+
required: ['query']
|
|
261
333
|
}
|
|
262
334
|
}
|
|
263
|
-
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
properties: {
|
|
270
|
-
conversationId: { type: 'string', description: 'Conversation UUID' },
|
|
271
|
-
type: { type: 'string', enum: ['incremental', 'rolling', 'final'], description: 'Summary type' }
|
|
272
|
-
},
|
|
273
|
-
required: ['conversationId', 'type']
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
name: 'protect_memory',
|
|
278
|
-
description: 'Mark memory as protected (cannot be evicted)',
|
|
279
|
-
inputSchema: {
|
|
280
|
-
type: 'object',
|
|
281
|
-
properties: {
|
|
282
|
-
memoryId: { type: 'string', description: 'Memory UUID' },
|
|
283
|
-
reason: { type: 'string', description: 'Protection reason' }
|
|
284
|
-
},
|
|
285
|
-
required: ['memoryId', 'reason']
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: 'pin_memory',
|
|
290
|
-
description: 'Pin memory for automatic injection into context',
|
|
291
|
-
inputSchema: {
|
|
292
|
-
type: 'object',
|
|
293
|
-
properties: {
|
|
294
|
-
memoryId: { type: 'string', description: 'Memory UUID' }
|
|
295
|
-
},
|
|
296
|
-
required: ['memoryId']
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
name: 'get_related',
|
|
301
|
-
description: 'Find memories related to a specific memory by ID. Use this for iterative search - if a search result is relevant, find similar memories without needing new queries.',
|
|
302
|
-
inputSchema: {
|
|
303
|
-
type: 'object',
|
|
304
|
-
properties: {
|
|
305
|
-
memoryId: { type: 'string', description: 'Memory UUID' },
|
|
306
|
-
limit: { type: 'number', description: 'Max results (default: 10)' }
|
|
307
|
-
},
|
|
308
|
-
required: ['memoryId']
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
// ============================================================================
|
|
312
|
-
// QMD Hybrid Search Tool (v0.7.0)
|
|
313
|
-
// ============================================================================
|
|
314
|
-
{
|
|
315
|
-
name: 'qmd_search',
|
|
316
|
-
description: 'Search memories using QMD hybrid search (BM25 + vector + rerank). Provides higher quality results when QMD is available. Falls back to standard search if QMD is not installed.',
|
|
317
|
-
inputSchema: {
|
|
318
|
-
type: 'object',
|
|
319
|
-
properties: {
|
|
320
|
-
query: { type: 'string', description: 'Search query' },
|
|
321
|
-
type: { type: 'string', enum: ['observation', 'fact', 'decision', 'context', 'preference'], description: 'Memory type filter' },
|
|
322
|
-
limit: { type: 'number', description: 'Max results (default: 10)', default: 10 },
|
|
323
|
-
useHybrid: { type: 'boolean', description: 'Use full hybrid pipeline with reranking (default: true)', default: true },
|
|
324
|
-
collection: { type: 'string', description: 'Override default collection mapping' }
|
|
325
|
-
},
|
|
326
|
-
required: ['query']
|
|
335
|
+
];
|
|
336
|
+
class Squish {
|
|
337
|
+
server;
|
|
338
|
+
constructor() {
|
|
339
|
+
this.server = new Server({ name: 'squish', version: VERSION }, { capabilities: { tools: {} } });
|
|
340
|
+
this.setup();
|
|
327
341
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const args = (req.params.arguments ?? {});
|
|
343
|
-
try {
|
|
344
|
-
switch (name) {
|
|
345
|
-
case 'core_memory':
|
|
346
|
-
return await this.handleCoreMemory(args);
|
|
347
|
-
case 'context_paging':
|
|
348
|
-
return await this.handleContextPaging(args);
|
|
349
|
-
case 'context_status': {
|
|
350
|
-
if (!args.sessionId || !args.projectId) {
|
|
351
|
-
throw new McpError(ErrorCode.InvalidParams, 'sessionId and projectId are required');
|
|
352
|
-
}
|
|
353
|
-
try {
|
|
342
|
+
setup() {
|
|
343
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
344
|
+
tools: TOOLS
|
|
345
|
+
}));
|
|
346
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
347
|
+
const { name } = req.params;
|
|
348
|
+
const args = (req.params.arguments ?? {});
|
|
349
|
+
try {
|
|
350
|
+
switch (name) {
|
|
351
|
+
case 'core_memory':
|
|
352
|
+
return await this.handleCoreMemory(args);
|
|
353
|
+
case 'context_paging':
|
|
354
|
+
return await this.handleContextPaging(args);
|
|
355
|
+
case 'context_status': {
|
|
354
356
|
const result = await getContextStatus(String(args.sessionId), String(args.projectId));
|
|
355
357
|
return this.jsonResponse({ ok: true, ...result });
|
|
356
358
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// Original Memory Tools
|
|
362
|
-
case 'remember': {
|
|
363
|
-
if (typeof args.content !== 'string' || !args.content) {
|
|
364
|
-
throw new McpError(ErrorCode.InvalidParams, 'content is required');
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
// Check if agent context is provided
|
|
368
|
-
if (args.agentId) {
|
|
369
|
-
const memoryId = await storeAgentMemory(args.content, {
|
|
370
|
-
agentId: args.agentId,
|
|
371
|
-
agentRole: args.agentRole,
|
|
372
|
-
}, {
|
|
373
|
-
type: args.type,
|
|
374
|
-
visibilityScope: args.visibilityScope,
|
|
375
|
-
sector: args.sector,
|
|
376
|
-
tags: args.tags,
|
|
377
|
-
metadata: args.metadata,
|
|
378
|
-
});
|
|
379
|
-
return this.jsonResponse({ ok: true, memoryId, agentContext: true });
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
// Standard memory storage
|
|
383
|
-
return this.jsonResponse({ ok: true, data: await rememberMemory(args) });
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
catch (dbError) {
|
|
387
|
-
if (isDatabaseUnavailableError(dbError)) {
|
|
388
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - memory storage disabled');
|
|
389
|
-
}
|
|
390
|
-
throw dbError;
|
|
359
|
+
case 'remember': {
|
|
360
|
+
return this.jsonResponse({ ok: true, data: await rememberMemory(args) });
|
|
391
361
|
}
|
|
392
|
-
|
|
393
|
-
case 'recall': {
|
|
394
|
-
if (!args.id) {
|
|
395
|
-
throw new McpError(ErrorCode.InvalidParams, 'id is required');
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
362
|
+
case 'recall': {
|
|
398
363
|
const memory = await getMemoryById(String(args.id));
|
|
399
364
|
return this.jsonResponse({ ok: true, found: !!memory, data: memory });
|
|
400
365
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - memory retrieval disabled');
|
|
404
|
-
}
|
|
405
|
-
throw dbError;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
case 'search': {
|
|
409
|
-
const scope = args.scope || 'memories';
|
|
410
|
-
try {
|
|
411
|
-
if (scope === 'memories') {
|
|
412
|
-
if (typeof args.query !== 'string' || !args.query) {
|
|
413
|
-
throw new McpError(ErrorCode.InvalidParams, 'query is required for memory search');
|
|
414
|
-
}
|
|
415
|
-
return this.jsonResponse({ ok: true, scope: 'memories', data: await searchMemories(args) });
|
|
416
|
-
}
|
|
417
|
-
else if (scope === 'conversations') {
|
|
418
|
-
if (typeof args.query !== 'string' || !args.query) {
|
|
419
|
-
throw new McpError(ErrorCode.InvalidParams, 'query is required for conversation search');
|
|
420
|
-
}
|
|
421
|
-
return this.jsonResponse({ ok: true, scope: 'conversations', data: await searchConversations(args) });
|
|
422
|
-
}
|
|
423
|
-
else if (scope === 'recent') {
|
|
424
|
-
return this.jsonResponse({ ok: true, scope: 'recent', data: await getRecentConversations(args) });
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown scope: ${scope}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
catch (dbError) {
|
|
431
|
-
if (isDatabaseUnavailableError(dbError)) {
|
|
432
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - search disabled');
|
|
433
|
-
}
|
|
434
|
-
throw dbError;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
case 'observe': {
|
|
438
|
-
if (!args.type || !args.action || !args.summary) {
|
|
439
|
-
throw new McpError(ErrorCode.InvalidParams, 'type, action, and summary are required');
|
|
366
|
+
case 'search': {
|
|
367
|
+
return this.jsonResponse({ ok: true, data: await searchMemories(args) });
|
|
440
368
|
}
|
|
441
|
-
|
|
369
|
+
case 'observe':
|
|
442
370
|
return this.jsonResponse({ ok: true, data: await createObservation(args) });
|
|
443
|
-
|
|
444
|
-
catch (dbError) {
|
|
445
|
-
if (isDatabaseUnavailableError(dbError)) {
|
|
446
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - observation storage disabled');
|
|
447
|
-
}
|
|
448
|
-
throw dbError;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
case 'context': {
|
|
452
|
-
if (!args.project) {
|
|
453
|
-
throw new McpError(ErrorCode.InvalidParams, 'project is required');
|
|
454
|
-
}
|
|
455
|
-
try {
|
|
371
|
+
case 'context':
|
|
456
372
|
return this.jsonResponse({ ok: true, data: await getProjectContext(args) });
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (isDatabaseUnavailableError(dbError)) {
|
|
460
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - context retrieval disabled');
|
|
461
|
-
}
|
|
462
|
-
throw dbError;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
case 'init': {
|
|
466
|
-
const { projectPath } = args;
|
|
467
|
-
const targetPath = projectPath || process.cwd();
|
|
468
|
-
const { ensureProject } = await import('./core/projects.js');
|
|
469
|
-
const { ensureDataDirectory } = await import('./db/bootstrap.js');
|
|
470
|
-
const path = await import('path');
|
|
471
|
-
try {
|
|
472
|
-
// Ensure data directory exists
|
|
373
|
+
case 'init': {
|
|
374
|
+
const { ensureProject } = await import('./core/projects.js');
|
|
473
375
|
await ensureDataDirectory();
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (!project) {
|
|
477
|
-
return this.jsonResponse({
|
|
478
|
-
success: false,
|
|
479
|
-
error: 'Failed to create project',
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
return this.jsonResponse({
|
|
483
|
-
success: true,
|
|
484
|
-
message: `Squish initialized for project: ${project.name}`,
|
|
485
|
-
project: {
|
|
486
|
-
id: project.id,
|
|
487
|
-
name: project.name,
|
|
488
|
-
path: project.path,
|
|
489
|
-
},
|
|
490
|
-
nextSteps: [
|
|
491
|
-
'Squish is now ready to capture and store memories',
|
|
492
|
-
'Use /squish:remember to store important information',
|
|
493
|
-
'Use /squish:search to find stored memories',
|
|
494
|
-
'Use /squish:core_memory to view/edit your always-visible context',
|
|
495
|
-
],
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
catch (error) {
|
|
499
|
-
return this.jsonResponse({
|
|
500
|
-
success: false,
|
|
501
|
-
error: error instanceof Error ? error.message : String(error),
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
case 'health':
|
|
506
|
-
return this.health();
|
|
507
|
-
case 'merge':
|
|
508
|
-
return await this.handleMerge(args);
|
|
509
|
-
case 'lifecycle': {
|
|
510
|
-
const { project } = args;
|
|
511
|
-
const result = await forceLifecycleMaintenance(project);
|
|
512
|
-
return this.jsonResponse({
|
|
513
|
-
success: true,
|
|
514
|
-
message: 'Lifecycle maintenance completed',
|
|
515
|
-
stats: result,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
case 'summarize_session': {
|
|
519
|
-
const { conversationId, type } = args;
|
|
520
|
-
if (!conversationId || !type) {
|
|
521
|
-
throw new McpError(ErrorCode.InvalidParams, 'conversationId and type are required');
|
|
522
|
-
}
|
|
523
|
-
const result = await summarizeSession(conversationId, type);
|
|
524
|
-
return this.jsonResponse({
|
|
525
|
-
success: true,
|
|
526
|
-
summaryId: result.summaryId,
|
|
527
|
-
tokensSaved: result.tokensSaved,
|
|
528
|
-
message: `Session summarized with type: ${type}`,
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
case 'protect_memory': {
|
|
532
|
-
const { memoryId, reason } = args;
|
|
533
|
-
if (!memoryId || !reason) {
|
|
534
|
-
throw new McpError(ErrorCode.InvalidParams, 'memoryId and reason are required');
|
|
376
|
+
const project = await ensureProject(args.projectPath || process.cwd());
|
|
377
|
+
return this.jsonResponse({ success: true, project });
|
|
535
378
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
case 'pin_memory': {
|
|
544
|
-
const { memoryId } = args;
|
|
545
|
-
if (!memoryId) {
|
|
546
|
-
throw new McpError(ErrorCode.InvalidParams, 'memoryId is required');
|
|
547
|
-
}
|
|
548
|
-
await pinMemory(memoryId);
|
|
549
|
-
return this.jsonResponse({
|
|
550
|
-
success: true,
|
|
551
|
-
memoryId,
|
|
552
|
-
message: 'Memory pinned for auto-injection',
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
case 'get_related': {
|
|
556
|
-
const { memoryId, limit } = args;
|
|
557
|
-
if (!memoryId) {
|
|
558
|
-
throw new McpError(ErrorCode.InvalidParams, 'memoryId is required');
|
|
559
|
-
}
|
|
560
|
-
const related = await getRelatedMemories(memoryId, limit || 10);
|
|
561
|
-
return this.jsonResponse({
|
|
562
|
-
success: true,
|
|
563
|
-
memoryId,
|
|
564
|
-
relatedCount: related.length,
|
|
565
|
-
memories: related,
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
case 'qmd_search': {
|
|
569
|
-
if (typeof args.query !== 'string' || !args.query) {
|
|
570
|
-
throw new McpError(ErrorCode.InvalidParams, 'query is required');
|
|
571
|
-
}
|
|
572
|
-
try {
|
|
379
|
+
case 'health':
|
|
380
|
+
return this.health();
|
|
381
|
+
case 'merge':
|
|
382
|
+
return await this.handleMerge(args);
|
|
383
|
+
case 'qmd_search': {
|
|
573
384
|
const available = await isQMDAvailable();
|
|
574
385
|
if (!available) {
|
|
575
|
-
return this.jsonResponse({
|
|
576
|
-
ok: true,
|
|
577
|
-
qmdAvailable: false,
|
|
578
|
-
message: 'QMD is not installed. Install with: bun install -g qmd',
|
|
579
|
-
fallback: 'Using standard Squish search',
|
|
580
|
-
data: await searchMemories(args)
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
const results = await searchWithQMD({
|
|
584
|
-
query: args.query,
|
|
585
|
-
type: args.type,
|
|
586
|
-
limit: args.limit || 10,
|
|
587
|
-
useHybrid: args.useHybrid ?? true,
|
|
588
|
-
collection: args.collection
|
|
589
|
-
});
|
|
590
|
-
return this.jsonResponse({
|
|
591
|
-
ok: true,
|
|
592
|
-
qmdAvailable: true,
|
|
593
|
-
data: results
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
catch (dbError) {
|
|
597
|
-
if (isDatabaseUnavailableError(dbError)) {
|
|
598
|
-
throw new McpError(ErrorCode.InternalError, 'Database unavailable - search disabled');
|
|
386
|
+
return this.jsonResponse({ ok: true, qmdAvailable: false, data: await searchMemories(args) });
|
|
599
387
|
}
|
|
600
|
-
|
|
388
|
+
return this.jsonResponse({ ok: true, qmdAvailable: true, data: await searchWithQMD(args) });
|
|
601
389
|
}
|
|
390
|
+
default:
|
|
391
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
602
392
|
}
|
|
603
|
-
default:
|
|
604
|
-
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
605
393
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
throw
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
process.on('SIGINT', () => this.shutdown());
|
|
616
|
-
process.on('SIGTERM', () => this.shutdown());
|
|
617
|
-
}
|
|
618
|
-
jsonResponse(payload) {
|
|
619
|
-
return {
|
|
620
|
-
content: [{
|
|
621
|
-
type: 'text',
|
|
622
|
-
text: JSON.stringify(payload, null, 2)
|
|
623
|
-
}]
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
async handleCoreMemory(args) {
|
|
627
|
-
const action = args.action;
|
|
628
|
-
if (!args.projectId) {
|
|
629
|
-
throw new McpError(ErrorCode.InvalidParams, 'projectId is required');
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (error instanceof McpError)
|
|
396
|
+
throw error;
|
|
397
|
+
throw new McpError(ErrorCode.InternalError, `Tool '${name}' failed`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
this.server.onerror = (e) => logger.error('MCP Server error', e);
|
|
401
|
+
process.on('SIGINT', () => this.shutdown());
|
|
402
|
+
process.on('SIGTERM', () => this.shutdown());
|
|
630
403
|
}
|
|
631
|
-
|
|
632
|
-
|
|
404
|
+
jsonResponse(payload) {
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }]
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async handleCoreMemory(args) {
|
|
410
|
+
const action = args.action;
|
|
411
|
+
const projectId = String(args.projectId);
|
|
633
412
|
await initializeCoreMemory(projectId);
|
|
634
413
|
if (action === 'view') {
|
|
635
414
|
const content = await getCoreMemory(projectId);
|
|
@@ -637,148 +416,67 @@ class Squish {
|
|
|
637
416
|
return this.jsonResponse({ ok: true, action: 'view', content, stats });
|
|
638
417
|
}
|
|
639
418
|
if (action === 'edit') {
|
|
640
|
-
if (!args.section || typeof args.content !== 'string') {
|
|
641
|
-
throw new McpError(ErrorCode.InvalidParams, 'section and content are required for edit action');
|
|
642
|
-
}
|
|
643
419
|
const result = await editCoreMemorySection(projectId, args.section, String(args.content));
|
|
644
|
-
if (!result.success) {
|
|
645
|
-
throw new McpError(ErrorCode.InvalidParams, result.message || 'Edit failed');
|
|
646
|
-
}
|
|
647
420
|
return this.jsonResponse({ ok: true, action: 'edit', ...result });
|
|
648
421
|
}
|
|
649
422
|
if (action === 'append') {
|
|
650
|
-
if (!args.section || typeof args.text !== 'string') {
|
|
651
|
-
throw new McpError(ErrorCode.InvalidParams, 'section and text are required for append action');
|
|
652
|
-
}
|
|
653
423
|
const result = await appendCoreMemorySection(projectId, args.section, String(args.text));
|
|
654
|
-
if (!result.success) {
|
|
655
|
-
throw new McpError(ErrorCode.InvalidParams, result.message || 'Append failed');
|
|
656
|
-
}
|
|
657
424
|
return this.jsonResponse({ ok: true, action: 'append', ...result });
|
|
658
425
|
}
|
|
659
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown
|
|
660
|
-
}
|
|
661
|
-
catch (error) {
|
|
662
|
-
if (error instanceof McpError)
|
|
663
|
-
throw error;
|
|
664
|
-
throw new McpError(ErrorCode.InternalError, `Core memory failed: ${error.message}`);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
async handleContextPaging(args) {
|
|
668
|
-
const action = args.action;
|
|
669
|
-
if (!args.sessionId) {
|
|
670
|
-
throw new McpError(ErrorCode.InvalidParams, 'sessionId is required');
|
|
426
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
671
427
|
}
|
|
672
|
-
|
|
673
|
-
|
|
428
|
+
async handleContextPaging(args) {
|
|
429
|
+
const action = args.action;
|
|
430
|
+
const sessionId = String(args.sessionId);
|
|
674
431
|
if (action === 'load') {
|
|
675
|
-
|
|
676
|
-
throw new McpError(ErrorCode.InvalidParams, 'memoryId is required for load action');
|
|
677
|
-
}
|
|
678
|
-
const result = await loadMemoryToContext(sessionId, String(args.memoryId));
|
|
679
|
-
if (!result.success) {
|
|
680
|
-
throw new McpError(ErrorCode.InvalidParams, result.message || 'Load failed');
|
|
681
|
-
}
|
|
682
|
-
return this.jsonResponse({ ok: true, action: 'load', ...result });
|
|
432
|
+
return this.jsonResponse(await loadMemoryToContext(sessionId, String(args.memoryId)));
|
|
683
433
|
}
|
|
684
434
|
if (action === 'evict') {
|
|
685
|
-
|
|
686
|
-
throw new McpError(ErrorCode.InvalidParams, 'memoryId is required for evict action');
|
|
687
|
-
}
|
|
688
|
-
const result = await evictMemoryFromContext(sessionId, String(args.memoryId));
|
|
689
|
-
if (!result.success) {
|
|
690
|
-
throw new McpError(ErrorCode.InvalidParams, result.message || 'Evict failed');
|
|
691
|
-
}
|
|
692
|
-
return this.jsonResponse({ ok: true, action: 'evict', ...result });
|
|
435
|
+
return this.jsonResponse(await evictMemoryFromContext(sessionId, String(args.memoryId)));
|
|
693
436
|
}
|
|
694
437
|
if (action === 'view') {
|
|
695
|
-
|
|
696
|
-
return this.jsonResponse({ ok: true, action: 'view', ...result });
|
|
438
|
+
return this.jsonResponse(await viewLoadedMemories(sessionId));
|
|
697
439
|
}
|
|
698
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown
|
|
440
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
699
441
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
throw new McpError(ErrorCode.InternalError, `Context paging failed: ${error.message}`);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
async handleMerge(args) {
|
|
707
|
-
const action = args.action;
|
|
708
|
-
try {
|
|
709
|
-
if (action === 'detect') {
|
|
442
|
+
async handleMerge(args) {
|
|
443
|
+
const action = args.action;
|
|
444
|
+
if (action === 'detect')
|
|
710
445
|
return this.jsonResponse(await handleDetectDuplicates(args));
|
|
711
|
-
|
|
712
|
-
if (action === 'list') {
|
|
446
|
+
if (action === 'list')
|
|
713
447
|
return this.jsonResponse(await handleListProposals(args));
|
|
714
|
-
|
|
715
|
-
if (action === 'preview') {
|
|
448
|
+
if (action === 'preview')
|
|
716
449
|
return this.jsonResponse(await handlePreviewMerge(args));
|
|
717
|
-
|
|
718
|
-
if (action === 'stats') {
|
|
450
|
+
if (action === 'stats')
|
|
719
451
|
return this.jsonResponse(await handleGetMergeStats(args));
|
|
720
|
-
|
|
721
|
-
if (action === 'approve') {
|
|
452
|
+
if (action === 'approve')
|
|
722
453
|
return this.jsonResponse(await handleApproveMerge(args));
|
|
723
|
-
|
|
724
|
-
if (action === 'reject') {
|
|
454
|
+
if (action === 'reject')
|
|
725
455
|
return this.jsonResponse(await handleRejectMerge(args));
|
|
726
|
-
|
|
727
|
-
if (action === 'reverse') {
|
|
456
|
+
if (action === 'reverse')
|
|
728
457
|
return this.jsonResponse(await handleReverseMerge(args));
|
|
729
|
-
}
|
|
730
|
-
throw new McpError(ErrorCode.InvalidParams, `Unknown merge action: ${action}`);
|
|
458
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
|
|
731
459
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
throw new McpError(ErrorCode.InternalError, `Merge operation failed: ${error.message}`);
|
|
460
|
+
async shutdown() {
|
|
461
|
+
await closeCache();
|
|
462
|
+
process.exit(0);
|
|
736
463
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
try {
|
|
746
|
-
dbOk = await checkDatabaseHealth();
|
|
747
|
-
dbStatus = dbOk ? 'ok' : 'unavailable';
|
|
464
|
+
async health() {
|
|
465
|
+
const dbOk = await checkDatabaseHealth();
|
|
466
|
+
const redisOk = await checkRedisHealth();
|
|
467
|
+
return this.jsonResponse({
|
|
468
|
+
version: VERSION,
|
|
469
|
+
mode: config.isTeamMode ? 'team' : 'local',
|
|
470
|
+
status: dbOk ? 'ok' : 'error',
|
|
471
|
+
});
|
|
748
472
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
dbStatus = 'error';
|
|
755
|
-
}
|
|
473
|
+
async run() {
|
|
474
|
+
const transport = new StdioServerTransport();
|
|
475
|
+
await this.server.connect(transport);
|
|
476
|
+
logger.info(`v${VERSION}`);
|
|
477
|
+
startWebServer();
|
|
756
478
|
}
|
|
757
|
-
const redisOk = await checkRedisHealth();
|
|
758
|
-
const overallStatus = determineOverallStatus(dbStatus, redisOk);
|
|
759
|
-
return this.jsonResponse({
|
|
760
|
-
version: VERSION,
|
|
761
|
-
mode: config.isTeamMode ? 'team' : 'local',
|
|
762
|
-
database: config.isTeamMode ? 'postgresql' : 'sqlite',
|
|
763
|
-
cache: config.redisEnabled ? 'redis' : 'memory',
|
|
764
|
-
embeddingsProvider: config.embeddingsProvider,
|
|
765
|
-
status: overallStatus,
|
|
766
|
-
checks: {
|
|
767
|
-
database: dbStatus,
|
|
768
|
-
cache: redisOk ? 'ok' : 'error'
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
async run() {
|
|
773
|
-
const transport = new StdioServerTransport();
|
|
774
|
-
await this.server.connect(transport);
|
|
775
|
-
logger.info(`v${VERSION}`);
|
|
776
|
-
// Start web UI server in background
|
|
777
|
-
startWebServer();
|
|
778
479
|
}
|
|
480
|
+
new Squish().run();
|
|
779
481
|
}
|
|
780
|
-
new Squish().run().catch((e) => {
|
|
781
|
-
logger.error('Fatal error', e);
|
|
782
|
-
process.exit(1);
|
|
783
|
-
});
|
|
784
482
|
//# sourceMappingURL=index.js.map
|