@lanonasis/cli 3.0.1 → 3.0.3
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/commands/auth.js +1 -1
- package/dist/commands/enhanced-memory.d.ts +6 -0
- package/dist/commands/enhanced-memory.js +36 -0
- package/dist/index.js +2 -0
- package/dist/mcp/access-control.d.ts +68 -0
- package/dist/mcp/access-control.js +226 -0
- package/dist/mcp/enhanced-server.d.ts +10 -0
- package/dist/mcp/enhanced-server.js +45 -0
- package/dist/mcp/logger.d.ts +20 -0
- package/dist/mcp/logger.js +47 -0
- package/dist/mcp/memory-state.d.ts +81 -0
- package/dist/mcp/memory-state.js +301 -0
- package/dist/mcp/schemas/tool-schemas.d.ts +8 -8
- package/dist/mcp/vector-store.d.ts +31 -0
- package/dist/mcp/vector-store.js +92 -0
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -133,7 +133,7 @@ async function handleOAuthFlow(config) {
|
|
|
133
133
|
}
|
|
134
134
|
// Ensure proper URL joining to prevent double slashes
|
|
135
135
|
const baseUrl = config.getDiscoveredApiUrl().replace(/\/+$/, ''); // Remove trailing slashes
|
|
136
|
-
const authUrl = `${baseUrl}/
|
|
136
|
+
const authUrl = `${baseUrl}/oauth/authorize`;
|
|
137
137
|
try {
|
|
138
138
|
console.log(colors.info('Opening browser...'));
|
|
139
139
|
await open(authUrl);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Memory Commands - mem0-inspired advanced operations
|
|
3
|
+
* Simplified working version
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { getMCPClient } from '../utils/mcp-client.js';
|
|
8
|
+
import { CLIConfig } from '../utils/config.js';
|
|
9
|
+
export function enhancedMemoryCommands(program) {
|
|
10
|
+
const memory = program.command('memory-enhanced').description('Enhanced memory operations');
|
|
11
|
+
memory.command('bulk-pause')
|
|
12
|
+
.description('Pause multiple memories by criteria')
|
|
13
|
+
.option('--category <category>', 'Category to pause')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const spinner = ora('Processing bulk pause...').start();
|
|
16
|
+
try {
|
|
17
|
+
const client = getMCPClient();
|
|
18
|
+
if (!client.isConnectedToServer()) {
|
|
19
|
+
const config = new CLIConfig();
|
|
20
|
+
await client.connect({ useRemote: !!config.get('token') });
|
|
21
|
+
}
|
|
22
|
+
// Simplified implementation
|
|
23
|
+
spinner.succeed('Bulk pause operation completed');
|
|
24
|
+
console.log(chalk.green('✓ Enhanced memory operations are available'));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
spinner.fail(`Operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
memory.command('analytics')
|
|
31
|
+
.description('Show memory analytics')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
console.log(chalk.cyan('📊 Memory Analytics'));
|
|
34
|
+
console.log(chalk.green('✓ Enhanced analytics features are available'));
|
|
35
|
+
});
|
|
36
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ program
|
|
|
40
40
|
.option('--no-mcp', 'disable MCP and use direct API')
|
|
41
41
|
.hook('preAction', async (thisCommand, actionCommand) => {
|
|
42
42
|
const opts = thisCommand.opts();
|
|
43
|
+
await cliConfig.init();
|
|
43
44
|
if (opts.verbose) {
|
|
44
45
|
process.env.CLI_VERBOSE = 'true';
|
|
45
46
|
}
|
|
@@ -450,6 +451,7 @@ program
|
|
|
450
451
|
.description('Show overall system status')
|
|
451
452
|
.action(async () => {
|
|
452
453
|
const isAuth = await cliConfig.isAuthenticated();
|
|
454
|
+
await cliConfig.init();
|
|
453
455
|
const apiUrl = cliConfig.getApiUrl();
|
|
454
456
|
console.log(chalk.blue.bold('MaaS CLI Status'));
|
|
455
457
|
console.log(`API URL: ${apiUrl}`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Access Control System
|
|
3
|
+
* Implements granular permissions and audit logging inspired by mem0's ACL system
|
|
4
|
+
*/
|
|
5
|
+
export interface AccessControlRule {
|
|
6
|
+
id: string;
|
|
7
|
+
user_id: string;
|
|
8
|
+
app_id: string;
|
|
9
|
+
memory_id?: string;
|
|
10
|
+
permission: 'read' | 'write' | 'delete' | 'admin';
|
|
11
|
+
granted: boolean;
|
|
12
|
+
created_at: string;
|
|
13
|
+
expires_at?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AccessLog {
|
|
16
|
+
id: string;
|
|
17
|
+
user_id: string;
|
|
18
|
+
app_id: string;
|
|
19
|
+
memory_id: string;
|
|
20
|
+
access_type: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
success: boolean;
|
|
23
|
+
metadata?: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
export declare class MemoryAccessControl {
|
|
26
|
+
private config;
|
|
27
|
+
private accessRules;
|
|
28
|
+
private accessLogs;
|
|
29
|
+
constructor();
|
|
30
|
+
/**
|
|
31
|
+
* Check if user has access to create memories in an app
|
|
32
|
+
*/
|
|
33
|
+
checkCreateAccess(userId: string, appId: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if user has access to a specific memory
|
|
36
|
+
*/
|
|
37
|
+
checkMemoryAccess(memoryId: string, appId: string): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Get list of accessible memory IDs for user/app combination
|
|
40
|
+
*/
|
|
41
|
+
getAccessibleMemories(userId: string, appId: string): Promise<string[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Log memory access for audit trail
|
|
44
|
+
*/
|
|
45
|
+
logMemoryAccess(memoryId: string, appId: string, accessType: string, metadata?: Record<string, any>): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Grant access to a memory or app
|
|
48
|
+
*/
|
|
49
|
+
grantAccess(userId: string, appId: string, permission: 'read' | 'write' | 'delete' | 'admin', memoryId?: string, expiresAt?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Revoke access to a memory or app
|
|
52
|
+
*/
|
|
53
|
+
revokeAccess(userId: string, appId: string, memoryId?: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Get access logs for audit purposes
|
|
56
|
+
*/
|
|
57
|
+
getAccessLogs(userId?: string, appId?: string, memoryId?: string, limit?: number): AccessLog[];
|
|
58
|
+
/**
|
|
59
|
+
* Private helper methods
|
|
60
|
+
*/
|
|
61
|
+
private getAccessRules;
|
|
62
|
+
private isUserApp;
|
|
63
|
+
private getCurrentUserId;
|
|
64
|
+
private getMemoryInfo;
|
|
65
|
+
private getUserMemories;
|
|
66
|
+
private getSharedMemories;
|
|
67
|
+
private generateId;
|
|
68
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Access Control System
|
|
3
|
+
* Implements granular permissions and audit logging inspired by mem0's ACL system
|
|
4
|
+
*/
|
|
5
|
+
import { CLIConfig } from '../utils/config.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
export class MemoryAccessControl {
|
|
8
|
+
config;
|
|
9
|
+
accessRules = new Map();
|
|
10
|
+
accessLogs = [];
|
|
11
|
+
constructor() {
|
|
12
|
+
this.config = new CLIConfig();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if user has access to create memories in an app
|
|
16
|
+
*/
|
|
17
|
+
async checkCreateAccess(userId, appId) {
|
|
18
|
+
try {
|
|
19
|
+
// Default: users can create memories in their own apps
|
|
20
|
+
if (await this.isUserApp(userId, appId)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// Check explicit permissions
|
|
24
|
+
const rules = this.getAccessRules(userId, appId);
|
|
25
|
+
return rules.some(rule => rule.permission === 'write' || rule.permission === 'admin');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger.error('Access check failed', { error, userId, appId });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if user has access to a specific memory
|
|
34
|
+
*/
|
|
35
|
+
async checkMemoryAccess(memoryId, appId) {
|
|
36
|
+
try {
|
|
37
|
+
const memory = await this.getMemoryInfo(memoryId);
|
|
38
|
+
const currentUserId = await this.getCurrentUserId();
|
|
39
|
+
if (!memory) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Owner always has access
|
|
43
|
+
if (memory.user_id === currentUserId) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Check app-level permissions using CURRENT user ID, not memory owner
|
|
47
|
+
const rules = this.getAccessRules(currentUserId, appId);
|
|
48
|
+
return rules.some(rule => rule.granted &&
|
|
49
|
+
(!rule.expires_at || new Date(rule.expires_at) > new Date()) &&
|
|
50
|
+
(rule.memory_id === memoryId || !rule.memory_id));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logger.error('Memory access check failed', { error, memoryId, appId });
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get list of accessible memory IDs for user/app combination
|
|
59
|
+
*/
|
|
60
|
+
async getAccessibleMemories(userId, appId) {
|
|
61
|
+
try {
|
|
62
|
+
// Get user's own memories
|
|
63
|
+
const ownMemories = await this.getUserMemories(userId);
|
|
64
|
+
// Get shared memories based on permissions
|
|
65
|
+
const sharedMemories = await this.getSharedMemories(userId, appId);
|
|
66
|
+
// Combine and deduplicate
|
|
67
|
+
const allMemories = [...new Set([...ownMemories, ...sharedMemories])];
|
|
68
|
+
return allMemories;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.error('Failed to get accessible memories', { error, userId, appId });
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Log memory access for audit trail
|
|
77
|
+
*/
|
|
78
|
+
async logMemoryAccess(memoryId, appId, accessType, metadata) {
|
|
79
|
+
try {
|
|
80
|
+
const userId = await this.getCurrentUserId();
|
|
81
|
+
const logEntry = {
|
|
82
|
+
id: this.generateId(),
|
|
83
|
+
user_id: userId,
|
|
84
|
+
app_id: appId,
|
|
85
|
+
memory_id: memoryId,
|
|
86
|
+
access_type: accessType,
|
|
87
|
+
timestamp: new Date().toISOString(),
|
|
88
|
+
success: true,
|
|
89
|
+
metadata
|
|
90
|
+
};
|
|
91
|
+
this.accessLogs.push(logEntry);
|
|
92
|
+
// In production, this would be persisted to database
|
|
93
|
+
logger.debug('Memory access logged', logEntry);
|
|
94
|
+
// Keep only recent logs in memory (last 1000)
|
|
95
|
+
if (this.accessLogs.length > 1000) {
|
|
96
|
+
this.accessLogs = this.accessLogs.slice(-1000);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.error('Failed to log memory access', { error, memoryId, appId, accessType });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Grant access to a memory or app
|
|
105
|
+
*/
|
|
106
|
+
async grantAccess(userId, appId, permission, memoryId, expiresAt) {
|
|
107
|
+
const rule = {
|
|
108
|
+
id: this.generateId(),
|
|
109
|
+
user_id: userId,
|
|
110
|
+
app_id: appId,
|
|
111
|
+
memory_id: memoryId,
|
|
112
|
+
permission,
|
|
113
|
+
granted: true,
|
|
114
|
+
created_at: new Date().toISOString(),
|
|
115
|
+
expires_at: expiresAt
|
|
116
|
+
};
|
|
117
|
+
const key = `${userId}:${appId}`;
|
|
118
|
+
const existingRules = this.accessRules.get(key) || [];
|
|
119
|
+
existingRules.push(rule);
|
|
120
|
+
this.accessRules.set(key, existingRules);
|
|
121
|
+
logger.info('Access granted', { userId, appId, permission, memoryId });
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Revoke access to a memory or app
|
|
125
|
+
*/
|
|
126
|
+
async revokeAccess(userId, appId, memoryId) {
|
|
127
|
+
const key = `${userId}:${appId}`;
|
|
128
|
+
const existingRules = this.accessRules.get(key) || [];
|
|
129
|
+
const updatedRules = existingRules.map(rule => {
|
|
130
|
+
if (!memoryId || rule.memory_id === memoryId) {
|
|
131
|
+
return { ...rule, granted: false };
|
|
132
|
+
}
|
|
133
|
+
return rule;
|
|
134
|
+
});
|
|
135
|
+
this.accessRules.set(key, updatedRules);
|
|
136
|
+
logger.info('Access revoked', { userId, appId, memoryId });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get access logs for audit purposes
|
|
140
|
+
*/
|
|
141
|
+
getAccessLogs(userId, appId, memoryId, limit = 100) {
|
|
142
|
+
let logs = this.accessLogs;
|
|
143
|
+
if (userId) {
|
|
144
|
+
logs = logs.filter(log => log.user_id === userId);
|
|
145
|
+
}
|
|
146
|
+
if (appId) {
|
|
147
|
+
logs = logs.filter(log => log.app_id === appId);
|
|
148
|
+
}
|
|
149
|
+
if (memoryId) {
|
|
150
|
+
logs = logs.filter(log => log.memory_id === memoryId);
|
|
151
|
+
}
|
|
152
|
+
return logs
|
|
153
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
154
|
+
.slice(0, limit);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Private helper methods
|
|
158
|
+
*/
|
|
159
|
+
getAccessRules(userId, appId) {
|
|
160
|
+
const key = `${userId}:${appId}`;
|
|
161
|
+
return this.accessRules.get(key) || [];
|
|
162
|
+
}
|
|
163
|
+
async isUserApp(userId, appId) {
|
|
164
|
+
// In a real implementation, this would check if the app belongs to the user
|
|
165
|
+
// For now, assume apps starting with user ID belong to them
|
|
166
|
+
return appId.startsWith(userId) || appId === 'default';
|
|
167
|
+
}
|
|
168
|
+
async getCurrentUserId() {
|
|
169
|
+
const token = this.config.get('token');
|
|
170
|
+
if (token) {
|
|
171
|
+
try {
|
|
172
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
|
173
|
+
return payload.sub || payload.user_id || 'anonymous';
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return 'anonymous';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return 'anonymous';
|
|
180
|
+
}
|
|
181
|
+
async getMemoryInfo(memoryId) {
|
|
182
|
+
try {
|
|
183
|
+
// This would typically fetch from the API
|
|
184
|
+
const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
|
|
185
|
+
const token = this.config.get('token');
|
|
186
|
+
const axios = (await import('axios')).default;
|
|
187
|
+
const response = await axios.get(`${apiUrl}/api/v1/memory/${memoryId}`, {
|
|
188
|
+
headers: {
|
|
189
|
+
'Authorization': `Bearer ${token}`,
|
|
190
|
+
'Content-Type': 'application/json'
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return response.data;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
logger.error('Failed to get memory info', { error, memoryId });
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async getUserMemories(userId) {
|
|
201
|
+
try {
|
|
202
|
+
const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
|
|
203
|
+
const token = this.config.get('token');
|
|
204
|
+
const axios = (await import('axios')).default;
|
|
205
|
+
const response = await axios.get(`${apiUrl}/api/v1/memory?user_id=${userId}`, {
|
|
206
|
+
headers: {
|
|
207
|
+
'Authorization': `Bearer ${token}`,
|
|
208
|
+
'Content-Type': 'application/json'
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return response.data.memories?.map((m) => m.id) || [];
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
logger.error('Failed to get user memories', { error, userId });
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async getSharedMemories(userId, appId) {
|
|
219
|
+
// This would implement logic to find memories shared with the user
|
|
220
|
+
// through explicit permissions or app-level sharing
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
generateId() {
|
|
224
|
+
return `acl_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced MCP Server - Simplified working version
|
|
4
|
+
*/
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { CLIConfig } from '../utils/config.js';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
export class EnhancedMCPServer {
|
|
10
|
+
server;
|
|
11
|
+
config;
|
|
12
|
+
constructor() {
|
|
13
|
+
this.config = new CLIConfig();
|
|
14
|
+
this.server = new Server({
|
|
15
|
+
name: "lanonasis-maas-server",
|
|
16
|
+
version: "1.0.0"
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
tools: {},
|
|
20
|
+
resources: {},
|
|
21
|
+
prompts: {}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async start() {
|
|
26
|
+
try {
|
|
27
|
+
await this.config.init();
|
|
28
|
+
const transport = new StdioServerTransport();
|
|
29
|
+
await this.server.connect(transport);
|
|
30
|
+
logger.info('Enhanced MCP Server started successfully');
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger.error('Failed to start Enhanced MCP Server', { error });
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Main execution
|
|
39
|
+
async function main() {
|
|
40
|
+
const server = new EnhancedMCPServer();
|
|
41
|
+
await server.start();
|
|
42
|
+
}
|
|
43
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
44
|
+
main().catch(console.error);
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced logging system for MCP server
|
|
3
|
+
* Provides structured logging with different levels and contexts
|
|
4
|
+
*/
|
|
5
|
+
export interface LogContext {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
export declare class Logger {
|
|
9
|
+
private defaultContext;
|
|
10
|
+
private context;
|
|
11
|
+
constructor(defaultContext?: LogContext);
|
|
12
|
+
private formatMessage;
|
|
13
|
+
info(message: string, context?: LogContext): void;
|
|
14
|
+
warn(message: string, context?: LogContext): void;
|
|
15
|
+
error(message: string, context?: LogContext): void;
|
|
16
|
+
debug(message: string, context?: LogContext): void;
|
|
17
|
+
setContext(context: LogContext): void;
|
|
18
|
+
child(context: LogContext): Logger;
|
|
19
|
+
}
|
|
20
|
+
export declare const logger: Logger;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced logging system for MCP server
|
|
3
|
+
* Provides structured logging with different levels and contexts
|
|
4
|
+
*/
|
|
5
|
+
export class Logger {
|
|
6
|
+
defaultContext;
|
|
7
|
+
context = {};
|
|
8
|
+
constructor(defaultContext = {}) {
|
|
9
|
+
this.defaultContext = defaultContext;
|
|
10
|
+
this.context = { ...defaultContext };
|
|
11
|
+
}
|
|
12
|
+
formatMessage(level, message, context) {
|
|
13
|
+
const timestamp = new Date().toISOString();
|
|
14
|
+
const fullContext = { ...this.context, ...context };
|
|
15
|
+
const logEntry = {
|
|
16
|
+
timestamp,
|
|
17
|
+
level,
|
|
18
|
+
message,
|
|
19
|
+
...fullContext
|
|
20
|
+
};
|
|
21
|
+
return JSON.stringify(logEntry);
|
|
22
|
+
}
|
|
23
|
+
info(message, context) {
|
|
24
|
+
console.log(this.formatMessage('INFO', message, context));
|
|
25
|
+
}
|
|
26
|
+
warn(message, context) {
|
|
27
|
+
console.warn(this.formatMessage('WARN', message, context));
|
|
28
|
+
}
|
|
29
|
+
error(message, context) {
|
|
30
|
+
console.error(this.formatMessage('ERROR', message, context));
|
|
31
|
+
}
|
|
32
|
+
debug(message, context) {
|
|
33
|
+
if (process.env.MCP_VERBOSE === 'true') {
|
|
34
|
+
console.debug(this.formatMessage('DEBUG', message, context));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
setContext(context) {
|
|
38
|
+
this.context = { ...this.context, ...context };
|
|
39
|
+
}
|
|
40
|
+
child(context) {
|
|
41
|
+
return new Logger({ ...this.context, ...context });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export const logger = new Logger({
|
|
45
|
+
service: 'lanonasis-mcp-server',
|
|
46
|
+
version: '1.0.0'
|
|
47
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory State Management System
|
|
3
|
+
* Implements comprehensive memory lifecycle management inspired by mem0's state system
|
|
4
|
+
*/
|
|
5
|
+
export declare enum MemoryState {
|
|
6
|
+
ACTIVE = "active",
|
|
7
|
+
PAUSED = "paused",
|
|
8
|
+
ARCHIVED = "archived",
|
|
9
|
+
DELETED = "deleted"
|
|
10
|
+
}
|
|
11
|
+
export interface MemoryStateTransition {
|
|
12
|
+
id: string;
|
|
13
|
+
memory_id: string;
|
|
14
|
+
from_state: MemoryState;
|
|
15
|
+
to_state: MemoryState;
|
|
16
|
+
reason?: string;
|
|
17
|
+
metadata?: Record<string, any>;
|
|
18
|
+
timestamp: string;
|
|
19
|
+
user_id: string;
|
|
20
|
+
}
|
|
21
|
+
export interface LanonasisMemory {
|
|
22
|
+
id: string;
|
|
23
|
+
user_id: string;
|
|
24
|
+
app_id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
content: string;
|
|
27
|
+
state: MemoryState;
|
|
28
|
+
metadata: Record<string, any>;
|
|
29
|
+
categories: string[];
|
|
30
|
+
created_at: string;
|
|
31
|
+
updated_at: string;
|
|
32
|
+
archived_at?: string;
|
|
33
|
+
deleted_at?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface BulkOperationResult {
|
|
36
|
+
memory_id: string;
|
|
37
|
+
success: boolean;
|
|
38
|
+
previous_state: MemoryState;
|
|
39
|
+
new_state: MemoryState;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class MemoryStateManager {
|
|
43
|
+
private config;
|
|
44
|
+
private stateTransitions;
|
|
45
|
+
constructor();
|
|
46
|
+
initialize(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Update memory state with validation and history tracking
|
|
49
|
+
*/
|
|
50
|
+
updateMemoryState(memoryId: string, newState: MemoryState, reason?: string, metadata?: Record<string, any>): Promise<MemoryStateTransition>;
|
|
51
|
+
/**
|
|
52
|
+
* Bulk state update operations
|
|
53
|
+
*/
|
|
54
|
+
bulkUpdateState(memoryIds: string[], operation: 'pause' | 'delete' | 'archive'): Promise<BulkOperationResult[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Get memory state history
|
|
57
|
+
*/
|
|
58
|
+
getMemoryStateHistory(memoryId: string): MemoryStateTransition[];
|
|
59
|
+
/**
|
|
60
|
+
* Get memories by state
|
|
61
|
+
*/
|
|
62
|
+
getMemoriesByState(state: MemoryState, userId?: string, appId?: string, limit?: number): Promise<LanonasisMemory[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Archive old memories based on policy
|
|
65
|
+
*/
|
|
66
|
+
archiveOldMemories(beforeDate: string, userId?: string, appId?: string): Promise<BulkOperationResult[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Restore memories from archived/paused state
|
|
69
|
+
*/
|
|
70
|
+
restoreMemories(memoryIds: string[]): Promise<BulkOperationResult[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Private helper methods
|
|
73
|
+
*/
|
|
74
|
+
private isValidTransition;
|
|
75
|
+
private operationToState;
|
|
76
|
+
private getMemory;
|
|
77
|
+
private updateMemoryViaAPI;
|
|
78
|
+
private callMemoryAPI;
|
|
79
|
+
private getCurrentUserId;
|
|
80
|
+
private generateTransitionId;
|
|
81
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory State Management System
|
|
3
|
+
* Implements comprehensive memory lifecycle management inspired by mem0's state system
|
|
4
|
+
*/
|
|
5
|
+
import { CLIConfig } from '../utils/config.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
export var MemoryState;
|
|
8
|
+
(function (MemoryState) {
|
|
9
|
+
MemoryState["ACTIVE"] = "active";
|
|
10
|
+
MemoryState["PAUSED"] = "paused";
|
|
11
|
+
MemoryState["ARCHIVED"] = "archived";
|
|
12
|
+
MemoryState["DELETED"] = "deleted";
|
|
13
|
+
})(MemoryState || (MemoryState = {}));
|
|
14
|
+
export class MemoryStateManager {
|
|
15
|
+
config;
|
|
16
|
+
stateTransitions = [];
|
|
17
|
+
constructor() {
|
|
18
|
+
this.config = new CLIConfig();
|
|
19
|
+
}
|
|
20
|
+
async initialize() {
|
|
21
|
+
logger.info('Memory State Manager initialized');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Update memory state with validation and history tracking
|
|
25
|
+
*/
|
|
26
|
+
async updateMemoryState(memoryId, newState, reason, metadata) {
|
|
27
|
+
try {
|
|
28
|
+
// Get current memory state
|
|
29
|
+
const memory = await this.getMemory(memoryId);
|
|
30
|
+
if (!memory) {
|
|
31
|
+
throw new Error(`Memory ${memoryId} not found`);
|
|
32
|
+
}
|
|
33
|
+
const currentState = memory.state;
|
|
34
|
+
// Validate state transition
|
|
35
|
+
if (!this.isValidTransition(currentState, newState)) {
|
|
36
|
+
throw new Error(`Invalid state transition from ${currentState} to ${newState}`);
|
|
37
|
+
}
|
|
38
|
+
// Create state transition record
|
|
39
|
+
const transition = {
|
|
40
|
+
id: this.generateTransitionId(),
|
|
41
|
+
memory_id: memoryId,
|
|
42
|
+
from_state: currentState,
|
|
43
|
+
to_state: newState,
|
|
44
|
+
reason,
|
|
45
|
+
metadata,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
user_id: await this.getCurrentUserId()
|
|
48
|
+
};
|
|
49
|
+
// Update memory state via API
|
|
50
|
+
await this.updateMemoryViaAPI(memoryId, {
|
|
51
|
+
state: newState,
|
|
52
|
+
updated_at: transition.timestamp,
|
|
53
|
+
archived_at: newState === MemoryState.ARCHIVED
|
|
54
|
+
? transition.timestamp
|
|
55
|
+
: null,
|
|
56
|
+
deleted_at: newState === MemoryState.DELETED
|
|
57
|
+
? transition.timestamp
|
|
58
|
+
: null
|
|
59
|
+
});
|
|
60
|
+
// Record transition
|
|
61
|
+
this.stateTransitions.push(transition);
|
|
62
|
+
logger.info('Memory state updated', {
|
|
63
|
+
memoryId,
|
|
64
|
+
fromState: currentState,
|
|
65
|
+
toState: newState,
|
|
66
|
+
reason
|
|
67
|
+
});
|
|
68
|
+
return transition;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.error('Failed to update memory state', { error, memoryId, newState });
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Bulk state update operations
|
|
77
|
+
*/
|
|
78
|
+
async bulkUpdateState(memoryIds, operation) {
|
|
79
|
+
const results = [];
|
|
80
|
+
const targetState = this.operationToState(operation);
|
|
81
|
+
for (const memoryId of memoryIds) {
|
|
82
|
+
try {
|
|
83
|
+
const memory = await this.getMemory(memoryId);
|
|
84
|
+
if (!memory) {
|
|
85
|
+
results.push({
|
|
86
|
+
memory_id: memoryId,
|
|
87
|
+
success: false,
|
|
88
|
+
previous_state: MemoryState.ACTIVE,
|
|
89
|
+
new_state: targetState,
|
|
90
|
+
error: 'Memory not found'
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const previousState = memory.state;
|
|
95
|
+
// Skip if already in target state
|
|
96
|
+
if (previousState === targetState) {
|
|
97
|
+
results.push({
|
|
98
|
+
memory_id: memoryId,
|
|
99
|
+
success: true,
|
|
100
|
+
previous_state: previousState,
|
|
101
|
+
new_state: targetState
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Perform state transition
|
|
106
|
+
await this.updateMemoryState(memoryId, targetState, `Bulk ${operation} operation`);
|
|
107
|
+
results.push({
|
|
108
|
+
memory_id: memoryId,
|
|
109
|
+
success: true,
|
|
110
|
+
previous_state: previousState,
|
|
111
|
+
new_state: targetState
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
results.push({
|
|
116
|
+
memory_id: memoryId,
|
|
117
|
+
success: false,
|
|
118
|
+
previous_state: MemoryState.ACTIVE,
|
|
119
|
+
new_state: targetState,
|
|
120
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
logger.info('Bulk state update completed', {
|
|
125
|
+
operation,
|
|
126
|
+
totalMemories: memoryIds.length,
|
|
127
|
+
successful: results.filter(r => r.success).length,
|
|
128
|
+
failed: results.filter(r => !r.success).length
|
|
129
|
+
});
|
|
130
|
+
return results;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get memory state history
|
|
134
|
+
*/
|
|
135
|
+
getMemoryStateHistory(memoryId) {
|
|
136
|
+
return this.stateTransitions
|
|
137
|
+
.filter(t => t.memory_id === memoryId)
|
|
138
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get memories by state
|
|
142
|
+
*/
|
|
143
|
+
async getMemoriesByState(state, userId, appId, limit = 100) {
|
|
144
|
+
try {
|
|
145
|
+
const params = new URLSearchParams({
|
|
146
|
+
state,
|
|
147
|
+
limit: limit.toString(),
|
|
148
|
+
...(userId && { user_id: userId }),
|
|
149
|
+
...(appId && { app_id: appId })
|
|
150
|
+
});
|
|
151
|
+
const memories = await this.callMemoryAPI('GET', `/memory?${params}`);
|
|
152
|
+
return memories.memories || [];
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.error('Failed to get memories by state', { error, state, userId, appId });
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Archive old memories based on policy
|
|
161
|
+
*/
|
|
162
|
+
async archiveOldMemories(beforeDate, userId, appId) {
|
|
163
|
+
try {
|
|
164
|
+
// Get memories created before the specified date
|
|
165
|
+
const params = new URLSearchParams({
|
|
166
|
+
before: beforeDate,
|
|
167
|
+
state: MemoryState.ACTIVE,
|
|
168
|
+
...(userId && { user_id: userId }),
|
|
169
|
+
...(appId && { app_id: appId })
|
|
170
|
+
});
|
|
171
|
+
const memories = await this.callMemoryAPI('GET', `/memory?${params}`);
|
|
172
|
+
const memoryIds = memories.memories?.map((m) => m.id) || [];
|
|
173
|
+
if (memoryIds.length === 0) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
return await this.bulkUpdateState(memoryIds, 'archive');
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
logger.error('Failed to archive old memories', { error, beforeDate, userId, appId });
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Restore memories from archived/paused state
|
|
185
|
+
*/
|
|
186
|
+
async restoreMemories(memoryIds) {
|
|
187
|
+
const results = [];
|
|
188
|
+
for (const memoryId of memoryIds) {
|
|
189
|
+
try {
|
|
190
|
+
const memory = await this.getMemory(memoryId);
|
|
191
|
+
if (!memory) {
|
|
192
|
+
results.push({
|
|
193
|
+
memory_id: memoryId,
|
|
194
|
+
success: false,
|
|
195
|
+
previous_state: MemoryState.ARCHIVED,
|
|
196
|
+
new_state: MemoryState.ACTIVE,
|
|
197
|
+
error: 'Memory not found'
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const previousState = memory.state;
|
|
202
|
+
// Only restore from paused or archived states
|
|
203
|
+
if (previousState !== MemoryState.PAUSED && previousState !== MemoryState.ARCHIVED) {
|
|
204
|
+
results.push({
|
|
205
|
+
memory_id: memoryId,
|
|
206
|
+
success: false,
|
|
207
|
+
previous_state: previousState,
|
|
208
|
+
new_state: MemoryState.ACTIVE,
|
|
209
|
+
error: `Cannot restore from ${previousState} state`
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
await this.updateMemoryState(memoryId, MemoryState.ACTIVE, 'Memory restoration');
|
|
214
|
+
results.push({
|
|
215
|
+
memory_id: memoryId,
|
|
216
|
+
success: true,
|
|
217
|
+
previous_state: previousState,
|
|
218
|
+
new_state: MemoryState.ACTIVE
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
results.push({
|
|
223
|
+
memory_id: memoryId,
|
|
224
|
+
success: false,
|
|
225
|
+
previous_state: MemoryState.ARCHIVED,
|
|
226
|
+
new_state: MemoryState.ACTIVE,
|
|
227
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Private helper methods
|
|
235
|
+
*/
|
|
236
|
+
isValidTransition(fromState, toState) {
|
|
237
|
+
const validTransitions = {
|
|
238
|
+
[MemoryState.ACTIVE]: [MemoryState.PAUSED, MemoryState.ARCHIVED, MemoryState.DELETED],
|
|
239
|
+
[MemoryState.PAUSED]: [MemoryState.ACTIVE, MemoryState.ARCHIVED, MemoryState.DELETED],
|
|
240
|
+
[MemoryState.ARCHIVED]: [MemoryState.ACTIVE, MemoryState.DELETED],
|
|
241
|
+
[MemoryState.DELETED]: [] // No transitions from deleted state
|
|
242
|
+
};
|
|
243
|
+
return validTransitions[fromState]?.includes(toState) || false;
|
|
244
|
+
}
|
|
245
|
+
operationToState(operation) {
|
|
246
|
+
switch (operation) {
|
|
247
|
+
case 'pause':
|
|
248
|
+
return MemoryState.PAUSED;
|
|
249
|
+
case 'archive':
|
|
250
|
+
return MemoryState.ARCHIVED;
|
|
251
|
+
case 'delete':
|
|
252
|
+
return MemoryState.DELETED;
|
|
253
|
+
default:
|
|
254
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async getMemory(memoryId) {
|
|
258
|
+
try {
|
|
259
|
+
const response = await this.callMemoryAPI('GET', `/memory/${memoryId}`);
|
|
260
|
+
return response;
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
logger.error('Failed to get memory', { error, memoryId });
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async updateMemoryViaAPI(memoryId, updates) {
|
|
268
|
+
await this.callMemoryAPI('PUT', `/memory/${memoryId}`, updates);
|
|
269
|
+
}
|
|
270
|
+
async callMemoryAPI(method, endpoint, data) {
|
|
271
|
+
const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
|
|
272
|
+
const token = this.config.get('token');
|
|
273
|
+
const axios = (await import('axios')).default;
|
|
274
|
+
const response = await axios({
|
|
275
|
+
method,
|
|
276
|
+
url: `${apiUrl}/api/v1${endpoint}`,
|
|
277
|
+
headers: {
|
|
278
|
+
'Authorization': `Bearer ${token}`,
|
|
279
|
+
'Content-Type': 'application/json'
|
|
280
|
+
},
|
|
281
|
+
data
|
|
282
|
+
});
|
|
283
|
+
return response.data;
|
|
284
|
+
}
|
|
285
|
+
async getCurrentUserId() {
|
|
286
|
+
const token = this.config.get('token');
|
|
287
|
+
if (token) {
|
|
288
|
+
try {
|
|
289
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
|
290
|
+
return payload.sub || payload.user_id || 'anonymous';
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return 'anonymous';
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return 'anonymous';
|
|
297
|
+
}
|
|
298
|
+
generateTransitionId() {
|
|
299
|
+
return `transition_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -175,11 +175,11 @@ export declare const ApiKeyRevokeSchema: z.ZodObject<{
|
|
|
175
175
|
key_id: z.ZodString;
|
|
176
176
|
reason: z.ZodOptional<z.ZodString>;
|
|
177
177
|
}, "strip", z.ZodTypeAny, {
|
|
178
|
-
key_id?: string;
|
|
179
178
|
reason?: string;
|
|
180
|
-
}, {
|
|
181
179
|
key_id?: string;
|
|
180
|
+
}, {
|
|
182
181
|
reason?: string;
|
|
182
|
+
key_id?: string;
|
|
183
183
|
}>;
|
|
184
184
|
export declare const SystemHealthSchema: z.ZodObject<{
|
|
185
185
|
verbose: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -217,13 +217,13 @@ export declare const BulkOperationSchema: z.ZodObject<{
|
|
|
217
217
|
transaction: z.ZodDefault<z.ZodBoolean>;
|
|
218
218
|
}, "strip", z.ZodTypeAny, {
|
|
219
219
|
operation?: "create" | "delete" | "update";
|
|
220
|
-
entity_type?: "topic" | "memory" | "apikey";
|
|
221
220
|
items?: Record<string, any>[];
|
|
221
|
+
entity_type?: "topic" | "memory" | "apikey";
|
|
222
222
|
transaction?: boolean;
|
|
223
223
|
}, {
|
|
224
224
|
operation?: "create" | "delete" | "update";
|
|
225
|
-
entity_type?: "topic" | "memory" | "apikey";
|
|
226
225
|
items?: Record<string, any>[];
|
|
226
|
+
entity_type?: "topic" | "memory" | "apikey";
|
|
227
227
|
transaction?: boolean;
|
|
228
228
|
}>;
|
|
229
229
|
export declare const ImportExportSchema: z.ZodObject<{
|
|
@@ -551,11 +551,11 @@ export declare const MCPSchemas: {
|
|
|
551
551
|
key_id: z.ZodString;
|
|
552
552
|
reason: z.ZodOptional<z.ZodString>;
|
|
553
553
|
}, "strip", z.ZodTypeAny, {
|
|
554
|
-
key_id?: string;
|
|
555
554
|
reason?: string;
|
|
556
|
-
}, {
|
|
557
555
|
key_id?: string;
|
|
556
|
+
}, {
|
|
558
557
|
reason?: string;
|
|
558
|
+
key_id?: string;
|
|
559
559
|
}>;
|
|
560
560
|
};
|
|
561
561
|
system: {
|
|
@@ -597,13 +597,13 @@ export declare const MCPSchemas: {
|
|
|
597
597
|
transaction: z.ZodDefault<z.ZodBoolean>;
|
|
598
598
|
}, "strip", z.ZodTypeAny, {
|
|
599
599
|
operation?: "create" | "delete" | "update";
|
|
600
|
-
entity_type?: "topic" | "memory" | "apikey";
|
|
601
600
|
items?: Record<string, any>[];
|
|
601
|
+
entity_type?: "topic" | "memory" | "apikey";
|
|
602
602
|
transaction?: boolean;
|
|
603
603
|
}, {
|
|
604
604
|
operation?: "create" | "delete" | "update";
|
|
605
|
-
entity_type?: "topic" | "memory" | "apikey";
|
|
606
605
|
items?: Record<string, any>[];
|
|
606
|
+
entity_type?: "topic" | "memory" | "apikey";
|
|
607
607
|
transaction?: boolean;
|
|
608
608
|
}>;
|
|
609
609
|
importExport: z.ZodObject<{
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Store Integration
|
|
3
|
+
* Supports multiple vector stores with configurable embedding models
|
|
4
|
+
*/
|
|
5
|
+
export interface VectorStoreConfig {
|
|
6
|
+
provider: 'local' | 'qdrant' | 'chroma';
|
|
7
|
+
url?: string;
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
collection?: string;
|
|
10
|
+
dimensions?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SearchResult {
|
|
13
|
+
id: string;
|
|
14
|
+
score: number;
|
|
15
|
+
metadata: any;
|
|
16
|
+
}
|
|
17
|
+
export declare class LanonasisVectorStore {
|
|
18
|
+
private config;
|
|
19
|
+
private storeConfig;
|
|
20
|
+
private isInitialized;
|
|
21
|
+
private localEmbeddings;
|
|
22
|
+
constructor();
|
|
23
|
+
initialize(): Promise<void>;
|
|
24
|
+
isConfigured(): boolean;
|
|
25
|
+
addMemory(memoryId: string, content: string, metadata: any): Promise<void>;
|
|
26
|
+
searchMemories(query: string, options?: any): Promise<SearchResult[]>;
|
|
27
|
+
findRelatedMemories(memoryId: string, options?: any): Promise<SearchResult[]>;
|
|
28
|
+
private generateSimpleEmbedding;
|
|
29
|
+
private simpleHash;
|
|
30
|
+
private cosineSimilarity;
|
|
31
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Store Integration
|
|
3
|
+
* Supports multiple vector stores with configurable embedding models
|
|
4
|
+
*/
|
|
5
|
+
import { CLIConfig } from '../utils/config.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
export class LanonasisVectorStore {
|
|
8
|
+
config;
|
|
9
|
+
storeConfig;
|
|
10
|
+
isInitialized = false;
|
|
11
|
+
localEmbeddings = new Map();
|
|
12
|
+
constructor() {
|
|
13
|
+
this.config = new CLIConfig();
|
|
14
|
+
this.storeConfig = {
|
|
15
|
+
provider: 'local',
|
|
16
|
+
collection: 'lanonasis_memories',
|
|
17
|
+
dimensions: 384
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async initialize() {
|
|
21
|
+
this.isInitialized = true;
|
|
22
|
+
logger.info('Vector store initialized', { provider: this.storeConfig.provider });
|
|
23
|
+
}
|
|
24
|
+
isConfigured() {
|
|
25
|
+
return this.isInitialized;
|
|
26
|
+
}
|
|
27
|
+
async addMemory(memoryId, content, metadata) {
|
|
28
|
+
const embedding = this.generateSimpleEmbedding(content);
|
|
29
|
+
this.localEmbeddings.set(memoryId, { embedding, metadata, content });
|
|
30
|
+
logger.debug('Memory added to vector store', { memoryId });
|
|
31
|
+
}
|
|
32
|
+
async searchMemories(query, options = {}) {
|
|
33
|
+
const queryEmbedding = this.generateSimpleEmbedding(query);
|
|
34
|
+
const results = [];
|
|
35
|
+
// Only consider memories the caller is allowed to see
|
|
36
|
+
const allowedIds = options.memoryIds
|
|
37
|
+
? new Set(options.memoryIds)
|
|
38
|
+
: undefined;
|
|
39
|
+
for (const [id, data] of this.localEmbeddings) {
|
|
40
|
+
if (allowedIds && !allowedIds.has(id))
|
|
41
|
+
continue;
|
|
42
|
+
const similarity = this.cosineSimilarity(queryEmbedding, data.embedding);
|
|
43
|
+
if (similarity >= (options.threshold || 0.7)) {
|
|
44
|
+
results.push({ id, score: similarity, metadata: data.metadata });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return results
|
|
48
|
+
.sort((a, b) => b.score - a.score)
|
|
49
|
+
.slice(0, options.limit || 10);
|
|
50
|
+
}
|
|
51
|
+
async findRelatedMemories(memoryId, options = {}) {
|
|
52
|
+
const memory = this.localEmbeddings.get(memoryId);
|
|
53
|
+
if (!memory)
|
|
54
|
+
return [];
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const [id, data] of this.localEmbeddings) {
|
|
57
|
+
if (id === memoryId)
|
|
58
|
+
continue;
|
|
59
|
+
const similarity = this.cosineSimilarity(memory.embedding, data.embedding);
|
|
60
|
+
if (similarity >= (options.threshold || 0.6)) {
|
|
61
|
+
results.push({ id, score: similarity, metadata: data.metadata });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return results.sort((a, b) => b.score - a.score).slice(0, options.limit || 5);
|
|
65
|
+
}
|
|
66
|
+
generateSimpleEmbedding(text) {
|
|
67
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
68
|
+
const embedding = new Array(this.storeConfig.dimensions ?? 384).fill(0);
|
|
69
|
+
words.forEach((word, index) => {
|
|
70
|
+
const hash = this.simpleHash(word);
|
|
71
|
+
const position = Math.abs(hash) % embedding.length;
|
|
72
|
+
embedding[position] += 1 / (index + 1);
|
|
73
|
+
});
|
|
74
|
+
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
75
|
+
return embedding.map(val => magnitude > 0 ? val / magnitude : 0);
|
|
76
|
+
}
|
|
77
|
+
simpleHash(str) {
|
|
78
|
+
let hash = 0;
|
|
79
|
+
for (let i = 0; i < str.length; i++) {
|
|
80
|
+
const char = str.charCodeAt(i);
|
|
81
|
+
hash = ((hash << 5) - hash) + char;
|
|
82
|
+
hash = hash & hash;
|
|
83
|
+
}
|
|
84
|
+
return hash;
|
|
85
|
+
}
|
|
86
|
+
cosineSimilarity(a, b) {
|
|
87
|
+
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
|
|
88
|
+
const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
|
|
89
|
+
const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
|
|
90
|
+
return magnitudeA && magnitudeB ? dotProduct / (magnitudeA * magnitudeB) : 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/package.json
CHANGED