@prmichaelsen/remember-mcp 0.2.5 → 0.2.7
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/agent/progress.yaml +141 -30
- package/agent/tasks/task-20-fix-weaviate-v3-filters.md +450 -0
- package/dist/server-factory.js +582 -113
- package/dist/server.js +579 -113
- package/dist/services/preferences-database.service.d.ts +22 -0
- package/dist/tools/get-preferences.d.ts +41 -0
- package/dist/tools/search-memory.d.ts +1 -1
- package/dist/tools/set-preference.d.ts +185 -0
- package/dist/types/preferences.d.ts +284 -0
- package/dist/utils/weaviate-filters.d.ts +37 -0
- package/dist/utils/weaviate-filters.spec.d.ts +5 -0
- package/package.json +1 -1
- package/src/server-factory.ts +15 -0
- package/src/server.ts +15 -0
- package/src/services/preferences-database.service.ts +120 -0
- package/src/tools/create-memory.ts +1 -0
- package/src/tools/get-preferences.ts +111 -0
- package/src/tools/query-memory.ts +5 -57
- package/src/tools/search-memory.ts +52 -83
- package/src/tools/set-preference.ts +145 -0
- package/src/types/preferences.ts +280 -0
- package/src/utils/weaviate-filters.spec.ts +515 -0
- package/src/utils/weaviate-filters.ts +207 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preferences Database Service
|
|
3
|
+
* Handles all Firestore operations for user preferences
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getDocument, setDocument } from '../firestore/init.js';
|
|
7
|
+
import { getUserPreferencesPath } from '../firestore/paths.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import {
|
|
10
|
+
UserPreferences,
|
|
11
|
+
DEFAULT_PREFERENCES,
|
|
12
|
+
} from '../types/preferences.js';
|
|
13
|
+
|
|
14
|
+
export class PreferencesDatabaseService {
|
|
15
|
+
/**
|
|
16
|
+
* Get user preferences
|
|
17
|
+
* Returns defaults if preferences don't exist
|
|
18
|
+
*/
|
|
19
|
+
static async getPreferences(userId: string): Promise<UserPreferences> {
|
|
20
|
+
try {
|
|
21
|
+
const pathParts = getUserPreferencesPath(userId).split('/');
|
|
22
|
+
const docId = pathParts.pop()!;
|
|
23
|
+
const collectionPath = pathParts.join('/');
|
|
24
|
+
|
|
25
|
+
const doc = await getDocument(collectionPath, docId);
|
|
26
|
+
|
|
27
|
+
if (!doc) {
|
|
28
|
+
// Return defaults with user_id
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
return {
|
|
31
|
+
user_id: userId,
|
|
32
|
+
...DEFAULT_PREFERENCES,
|
|
33
|
+
created_at: now,
|
|
34
|
+
updated_at: now,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return doc as UserPreferences;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error('Failed to get preferences:', error);
|
|
41
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Update user preferences (partial update with merge)
|
|
47
|
+
* Creates with defaults if preferences don't exist
|
|
48
|
+
*/
|
|
49
|
+
static async updatePreferences(
|
|
50
|
+
userId: string,
|
|
51
|
+
updates: Partial<Omit<UserPreferences, 'user_id' | 'created_at'>>
|
|
52
|
+
): Promise<UserPreferences> {
|
|
53
|
+
try {
|
|
54
|
+
const pathParts = getUserPreferencesPath(userId).split('/');
|
|
55
|
+
const docId = pathParts.pop()!;
|
|
56
|
+
const collectionPath = pathParts.join('/');
|
|
57
|
+
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const doc = await getDocument(collectionPath, docId);
|
|
60
|
+
|
|
61
|
+
if (!doc) {
|
|
62
|
+
// Create with defaults + updates
|
|
63
|
+
const newPrefs: UserPreferences = {
|
|
64
|
+
user_id: userId,
|
|
65
|
+
...DEFAULT_PREFERENCES,
|
|
66
|
+
...updates,
|
|
67
|
+
created_at: now,
|
|
68
|
+
updated_at: now,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await setDocument(collectionPath, docId, newPrefs);
|
|
72
|
+
logger.info('Preferences created with defaults', { userId });
|
|
73
|
+
return newPrefs;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update existing preferences with merge
|
|
77
|
+
const updateData = {
|
|
78
|
+
...updates,
|
|
79
|
+
updated_at: now,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await setDocument(collectionPath, docId, updateData, { merge: true });
|
|
83
|
+
logger.info('Preferences updated', { userId });
|
|
84
|
+
|
|
85
|
+
// Return updated preferences
|
|
86
|
+
const updatedDoc = await getDocument(collectionPath, docId);
|
|
87
|
+
return updatedDoc as UserPreferences;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.error('Failed to update preferences:', error);
|
|
90
|
+
throw new Error(`Failed to update preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create preferences with defaults
|
|
96
|
+
*/
|
|
97
|
+
static async createPreferences(userId: string): Promise<UserPreferences> {
|
|
98
|
+
try {
|
|
99
|
+
const pathParts = getUserPreferencesPath(userId).split('/');
|
|
100
|
+
const docId = pathParts.pop()!;
|
|
101
|
+
const collectionPath = pathParts.join('/');
|
|
102
|
+
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
const preferences: UserPreferences = {
|
|
105
|
+
user_id: userId,
|
|
106
|
+
...DEFAULT_PREFERENCES,
|
|
107
|
+
created_at: now,
|
|
108
|
+
updated_at: now,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
await setDocument(collectionPath, docId, preferences);
|
|
112
|
+
logger.info('Preferences created', { userId });
|
|
113
|
+
|
|
114
|
+
return preferences;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error('Failed to create preferences:', error);
|
|
117
|
+
throw new Error(`Failed to create preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_get_preferences tool
|
|
3
|
+
* Retrieve user preferences with defaults
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PreferencesDatabaseService } from '../services/preferences-database.service.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import {
|
|
9
|
+
UserPreferences,
|
|
10
|
+
PreferenceCategory,
|
|
11
|
+
PREFERENCE_CATEGORIES,
|
|
12
|
+
getPreferenceDescription,
|
|
13
|
+
} from '../types/preferences.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tool definition for remember_get_preferences
|
|
17
|
+
*/
|
|
18
|
+
export const getPreferencesTool = {
|
|
19
|
+
name: 'remember_get_preferences',
|
|
20
|
+
description: `Get current user preferences.
|
|
21
|
+
|
|
22
|
+
Use this to understand user's current settings before suggesting changes
|
|
23
|
+
or to explain why system is behaving a certain way.
|
|
24
|
+
|
|
25
|
+
Returns the complete preferences object or filtered by category.
|
|
26
|
+
If preferences don't exist, returns defaults.
|
|
27
|
+
|
|
28
|
+
${getPreferenceDescription()}
|
|
29
|
+
`,
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
category: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
enum: ['templates', 'search', 'location', 'privacy', 'notifications', 'display'],
|
|
36
|
+
description: 'Optional category to filter preferences',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get preferences arguments
|
|
44
|
+
*/
|
|
45
|
+
export interface GetPreferencesArgs {
|
|
46
|
+
category?: PreferenceCategory;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get preferences result
|
|
51
|
+
*/
|
|
52
|
+
export interface GetPreferencesResult {
|
|
53
|
+
preferences: UserPreferences | Partial<UserPreferences>;
|
|
54
|
+
is_default: boolean;
|
|
55
|
+
message: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle remember_get_preferences tool
|
|
60
|
+
*/
|
|
61
|
+
export async function handleGetPreferences(
|
|
62
|
+
args: GetPreferencesArgs,
|
|
63
|
+
userId: string
|
|
64
|
+
): Promise<string> {
|
|
65
|
+
try {
|
|
66
|
+
const { category } = args;
|
|
67
|
+
|
|
68
|
+
logger.info('Getting preferences', { userId, category });
|
|
69
|
+
|
|
70
|
+
// Get preferences using service layer
|
|
71
|
+
const preferences = await PreferencesDatabaseService.getPreferences(userId);
|
|
72
|
+
|
|
73
|
+
// Check if these are defaults (no created_at means they were just generated)
|
|
74
|
+
const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
|
|
75
|
+
|
|
76
|
+
// Filter by category if requested
|
|
77
|
+
let result: UserPreferences | Partial<UserPreferences>;
|
|
78
|
+
let message: string;
|
|
79
|
+
|
|
80
|
+
if (category) {
|
|
81
|
+
if (!PREFERENCE_CATEGORIES.includes(category)) {
|
|
82
|
+
throw new Error(`Invalid category: ${category}. Valid categories: ${PREFERENCE_CATEGORIES.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
result = {
|
|
86
|
+
[category]: preferences[category],
|
|
87
|
+
};
|
|
88
|
+
message = isDefault
|
|
89
|
+
? `Showing default ${category} preferences (user has not customized preferences yet).`
|
|
90
|
+
: `Showing current ${category} preferences.`;
|
|
91
|
+
} else {
|
|
92
|
+
result = preferences;
|
|
93
|
+
message = isDefault
|
|
94
|
+
? 'Showing default preferences (user has not customized preferences yet).'
|
|
95
|
+
: 'Showing current user preferences.';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const response: GetPreferencesResult = {
|
|
99
|
+
preferences: result,
|
|
100
|
+
is_default: isDefault,
|
|
101
|
+
message,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
logger.info('Preferences retrieved successfully', { userId, category, isDefault });
|
|
105
|
+
|
|
106
|
+
return JSON.stringify(response, null, 2);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger.error('Failed to get preferences:', error);
|
|
109
|
+
throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Memory, SearchFilters } from '../types/memory.js';
|
|
7
7
|
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { buildCombinedSearchFilters } from '../utils/weaviate-filters.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Tool definition for remember_query_memory
|
|
@@ -144,58 +145,8 @@ export async function handleQueryMemory(
|
|
|
144
145
|
const includeContext = args.include_context ?? true;
|
|
145
146
|
const format = args.format ?? 'detailed';
|
|
146
147
|
|
|
147
|
-
// Build
|
|
148
|
-
const
|
|
149
|
-
{
|
|
150
|
-
path: 'doc_type',
|
|
151
|
-
operator: 'Equal',
|
|
152
|
-
valueText: 'memory',
|
|
153
|
-
},
|
|
154
|
-
];
|
|
155
|
-
|
|
156
|
-
// Add type filter
|
|
157
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
158
|
-
whereFilters.push({
|
|
159
|
-
path: 'type',
|
|
160
|
-
operator: 'ContainsAny',
|
|
161
|
-
valueTextArray: args.filters.types,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Add weight filter
|
|
166
|
-
if (args.filters?.weight_min !== undefined) {
|
|
167
|
-
whereFilters.push({
|
|
168
|
-
path: 'weight',
|
|
169
|
-
operator: 'GreaterThanEqual',
|
|
170
|
-
valueNumber: args.filters.weight_min,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Add trust filter
|
|
175
|
-
if (args.filters?.trust_min !== undefined) {
|
|
176
|
-
whereFilters.push({
|
|
177
|
-
path: 'trust',
|
|
178
|
-
operator: 'GreaterThanEqual',
|
|
179
|
-
valueNumber: args.filters.trust_min,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Add date range filters
|
|
184
|
-
if (args.filters?.date_from) {
|
|
185
|
-
whereFilters.push({
|
|
186
|
-
path: 'created_at',
|
|
187
|
-
operator: 'GreaterThanEqual',
|
|
188
|
-
valueDate: new Date(args.filters.date_from),
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (args.filters?.date_to) {
|
|
193
|
-
whereFilters.push({
|
|
194
|
-
path: 'created_at',
|
|
195
|
-
operator: 'LessThanEqual',
|
|
196
|
-
valueDate: new Date(args.filters.date_to),
|
|
197
|
-
});
|
|
198
|
-
}
|
|
148
|
+
// Build filters using v3 API - search both memories and relationships
|
|
149
|
+
const filters = buildCombinedSearchFilters(collection, args.filters);
|
|
199
150
|
|
|
200
151
|
// Build search options
|
|
201
152
|
const searchOptions: any = {
|
|
@@ -205,11 +156,8 @@ export async function handleQueryMemory(
|
|
|
205
156
|
};
|
|
206
157
|
|
|
207
158
|
// Add filters if present
|
|
208
|
-
if (
|
|
209
|
-
searchOptions.filters =
|
|
210
|
-
operator: 'And' as const,
|
|
211
|
-
operands: whereFilters,
|
|
212
|
-
} : whereFilters[0];
|
|
159
|
+
if (filters) {
|
|
160
|
+
searchOptions.filters = filters;
|
|
213
161
|
}
|
|
214
162
|
|
|
215
163
|
// Perform semantic search using nearText
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* remember_search_memory tool
|
|
3
|
-
* Search memories using hybrid semantic + keyword search
|
|
3
|
+
* Search memories AND relationships using hybrid semantic + keyword search
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Memory, SearchOptions, SearchResult, SearchFilters } from '../types/memory.js';
|
|
6
|
+
import type { Memory, Relationship, SearchOptions, SearchResult, SearchFilters } from '../types/memory.js';
|
|
7
7
|
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { buildCombinedSearchFilters, buildMemoryOnlyFilters } from '../utils/weaviate-filters.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Tool definition for remember_search_memory
|
|
12
13
|
*/
|
|
13
14
|
export const searchMemoryTool = {
|
|
14
15
|
name: 'remember_search_memory',
|
|
15
|
-
description: `Search memories using hybrid semantic and keyword search.
|
|
16
|
+
description: `Search memories AND relationships using hybrid semantic and keyword search.
|
|
17
|
+
|
|
18
|
+
By default, searches BOTH memories and relationships to provide comprehensive results.
|
|
19
|
+
Relationships contain valuable context in their observations.
|
|
16
20
|
|
|
17
21
|
Supports:
|
|
18
|
-
- Semantic search (meaning-based)
|
|
22
|
+
- Semantic search (meaning-based) across memory content and relationship observations
|
|
19
23
|
- Keyword search (exact matches)
|
|
20
24
|
- Hybrid search (balanced with alpha parameter)
|
|
21
25
|
- Filtering by type, tags, weight, trust, date range
|
|
22
|
-
-
|
|
26
|
+
- Returns both memories and relationships in separate arrays
|
|
23
27
|
|
|
24
28
|
Examples:
|
|
25
|
-
- "Find memories about camping trips"
|
|
26
|
-
- "Search for recipes I saved"
|
|
27
|
-
- "Show me notes from last week"
|
|
29
|
+
- "Find memories about camping trips" → returns memories + relationships about camping
|
|
30
|
+
- "Search for recipes I saved" → returns recipe memories + related relationships
|
|
31
|
+
- "Show me notes from last week" → returns notes + any relationships created that week
|
|
28
32
|
`,
|
|
29
33
|
inputSchema: {
|
|
30
34
|
type: 'object',
|
|
@@ -87,8 +91,8 @@ export const searchMemoryTool = {
|
|
|
87
91
|
},
|
|
88
92
|
include_relationships: {
|
|
89
93
|
type: 'boolean',
|
|
90
|
-
description: 'Include relationships in results. Default:
|
|
91
|
-
default:
|
|
94
|
+
description: 'Include relationships in results. Default: true (searches both memories and relationships)',
|
|
95
|
+
default: true,
|
|
92
96
|
},
|
|
93
97
|
},
|
|
94
98
|
required: ['query'],
|
|
@@ -103,65 +107,24 @@ export async function handleSearchMemory(
|
|
|
103
107
|
userId: string
|
|
104
108
|
): Promise<string> {
|
|
105
109
|
try {
|
|
106
|
-
|
|
110
|
+
const includeRelationships = args.include_relationships !== false; // Default true
|
|
111
|
+
|
|
112
|
+
logger.info('Searching memories and relationships', {
|
|
113
|
+
userId,
|
|
114
|
+
query: args.query,
|
|
115
|
+
includeRelationships
|
|
116
|
+
});
|
|
107
117
|
|
|
108
118
|
const collection = getMemoryCollection(userId);
|
|
109
119
|
const alpha = args.alpha ?? 0.7;
|
|
110
120
|
const limit = args.limit ?? 10;
|
|
111
121
|
const offset = args.offset ?? 0;
|
|
112
122
|
|
|
113
|
-
// Build
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
valueText: 'memory',
|
|
119
|
-
},
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
// Add type filter
|
|
123
|
-
if (args.filters?.types && args.filters.types.length > 0) {
|
|
124
|
-
whereFilters.push({
|
|
125
|
-
path: 'type',
|
|
126
|
-
operator: 'ContainsAny',
|
|
127
|
-
valueTextArray: args.filters.types,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Add weight filter
|
|
132
|
-
if (args.filters?.weight_min !== undefined) {
|
|
133
|
-
whereFilters.push({
|
|
134
|
-
path: 'weight',
|
|
135
|
-
operator: 'GreaterThanEqual',
|
|
136
|
-
valueNumber: args.filters.weight_min,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Add trust filter
|
|
141
|
-
if (args.filters?.trust_min !== undefined) {
|
|
142
|
-
whereFilters.push({
|
|
143
|
-
path: 'trust',
|
|
144
|
-
operator: 'GreaterThanEqual',
|
|
145
|
-
valueNumber: args.filters.trust_min,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Add date range filters
|
|
150
|
-
if (args.filters?.date_from) {
|
|
151
|
-
whereFilters.push({
|
|
152
|
-
path: 'created_at',
|
|
153
|
-
operator: 'GreaterThanEqual',
|
|
154
|
-
valueDate: new Date(args.filters.date_from),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (args.filters?.date_to) {
|
|
159
|
-
whereFilters.push({
|
|
160
|
-
path: 'created_at',
|
|
161
|
-
operator: 'LessThanEqual',
|
|
162
|
-
valueDate: new Date(args.filters.date_to),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
123
|
+
// Build filters using v3 API
|
|
124
|
+
// Use OR logic to search both memories and relationships
|
|
125
|
+
const filters = includeRelationships
|
|
126
|
+
? buildCombinedSearchFilters(collection, args.filters)
|
|
127
|
+
: buildMemoryOnlyFilters(collection, args.filters);
|
|
165
128
|
|
|
166
129
|
// Build search options
|
|
167
130
|
const searchOptions: any = {
|
|
@@ -170,11 +133,8 @@ export async function handleSearchMemory(
|
|
|
170
133
|
};
|
|
171
134
|
|
|
172
135
|
// Add filters if present
|
|
173
|
-
if (
|
|
174
|
-
searchOptions.filters =
|
|
175
|
-
operator: 'And' as const,
|
|
176
|
-
operands: whereFilters,
|
|
177
|
-
} : whereFilters[0];
|
|
136
|
+
if (filters) {
|
|
137
|
+
searchOptions.filters = filters;
|
|
178
138
|
}
|
|
179
139
|
|
|
180
140
|
// Perform hybrid search with Weaviate v3 API
|
|
@@ -183,29 +143,38 @@ export async function handleSearchMemory(
|
|
|
183
143
|
// Apply offset
|
|
184
144
|
const paginatedResults = results.objects.slice(offset);
|
|
185
145
|
|
|
186
|
-
//
|
|
187
|
-
const memories: Partial<Memory>[] =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
146
|
+
// Separate memories and relationships
|
|
147
|
+
const memories: Partial<Memory>[] = [];
|
|
148
|
+
const relationships: Partial<Relationship>[] = [];
|
|
149
|
+
|
|
150
|
+
for (const obj of paginatedResults) {
|
|
151
|
+
const doc: any = {
|
|
152
|
+
id: obj.uuid,
|
|
153
|
+
...obj.properties,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (doc.doc_type === 'memory') {
|
|
157
|
+
memories.push(doc as Memory);
|
|
158
|
+
} else if (doc.doc_type === 'relationship') {
|
|
159
|
+
relationships.push(doc as Relationship);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
191
162
|
|
|
192
163
|
// Build result
|
|
193
164
|
const searchResult: SearchResult = {
|
|
194
165
|
memories: memories as Memory[],
|
|
195
|
-
|
|
166
|
+
relationships: includeRelationships ? (relationships as Relationship[]) : undefined,
|
|
167
|
+
total: memories.length + relationships.length,
|
|
196
168
|
offset: offset,
|
|
197
169
|
limit: limit,
|
|
198
170
|
};
|
|
199
171
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
userId,
|
|
207
|
-
query: args.query,
|
|
208
|
-
results: memories.length
|
|
172
|
+
logger.info('Search completed', {
|
|
173
|
+
userId,
|
|
174
|
+
query: args.query,
|
|
175
|
+
memoriesFound: memories.length,
|
|
176
|
+
relationshipsFound: relationships.length,
|
|
177
|
+
total: searchResult.total
|
|
209
178
|
});
|
|
210
179
|
|
|
211
180
|
return JSON.stringify(searchResult, null, 2);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_set_preference tool
|
|
3
|
+
* Update user preferences through natural conversation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PreferencesDatabaseService } from '../services/preferences-database.service.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import {
|
|
9
|
+
UserPreferences,
|
|
10
|
+
getPreferenceDescription,
|
|
11
|
+
getPreferencesSchema,
|
|
12
|
+
} from '../types/preferences.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tool definition for remember_set_preference
|
|
16
|
+
*/
|
|
17
|
+
export const setPreferenceTool = {
|
|
18
|
+
name: 'remember_set_preference',
|
|
19
|
+
description: `Update user preferences for system behavior through natural conversation.
|
|
20
|
+
|
|
21
|
+
This tool allows bulk updates to user preferences. Provide a partial preferences object
|
|
22
|
+
with only the fields you want to update. All updates are merged with existing preferences.
|
|
23
|
+
|
|
24
|
+
${getPreferenceDescription()}
|
|
25
|
+
|
|
26
|
+
Common examples:
|
|
27
|
+
- Disable template suggestions: { templates: { auto_suggest: false } }
|
|
28
|
+
- Change search defaults: { search: { default_limit: 20, default_alpha: 0.8 } }
|
|
29
|
+
- Update privacy: { privacy: { default_trust_level: 0.8 } }
|
|
30
|
+
- Suppress categories: { templates: { suppressed_categories: ["work", "personal"] } }
|
|
31
|
+
`,
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
preferences: {
|
|
36
|
+
...getPreferencesSchema(),
|
|
37
|
+
description: 'Partial preferences object with fields to update',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['preferences'],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set preference arguments
|
|
46
|
+
*/
|
|
47
|
+
export interface SetPreferenceArgs {
|
|
48
|
+
preferences: Partial<Omit<UserPreferences, 'user_id' | 'created_at' | 'updated_at'>>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set preference result
|
|
53
|
+
*/
|
|
54
|
+
export interface SetPreferenceResult {
|
|
55
|
+
success: boolean;
|
|
56
|
+
updated_preferences: UserPreferences;
|
|
57
|
+
message: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format a user-friendly message about the preference changes
|
|
63
|
+
*/
|
|
64
|
+
function formatPreferenceChangeMessage(updates: Partial<UserPreferences>): string {
|
|
65
|
+
const changes: string[] = [];
|
|
66
|
+
|
|
67
|
+
if (updates.templates) {
|
|
68
|
+
if (updates.templates.auto_suggest !== undefined) {
|
|
69
|
+
changes.push(
|
|
70
|
+
updates.templates.auto_suggest
|
|
71
|
+
? 'Template suggestions enabled'
|
|
72
|
+
: 'Template suggestions disabled'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (updates.templates.suppressed_categories) {
|
|
76
|
+
changes.push(`Suppressed categories: ${updates.templates.suppressed_categories.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (updates.search) {
|
|
81
|
+
if (updates.search.default_limit !== undefined) {
|
|
82
|
+
changes.push(`Search limit set to ${updates.search.default_limit}`);
|
|
83
|
+
}
|
|
84
|
+
if (updates.search.default_alpha !== undefined) {
|
|
85
|
+
changes.push(`Search alpha set to ${updates.search.default_alpha}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (updates.privacy) {
|
|
90
|
+
if (updates.privacy.default_trust_level !== undefined) {
|
|
91
|
+
changes.push(`Default trust level set to ${updates.privacy.default_trust_level}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (updates.location) {
|
|
96
|
+
if (updates.location.auto_capture !== undefined) {
|
|
97
|
+
changes.push(
|
|
98
|
+
updates.location.auto_capture
|
|
99
|
+
? 'Location auto-capture enabled'
|
|
100
|
+
: 'Location auto-capture disabled'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (changes.length === 0) {
|
|
106
|
+
return 'Preferences updated successfully';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return `Preferences updated: ${changes.join(', ')}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle remember_set_preference tool
|
|
114
|
+
*/
|
|
115
|
+
export async function handleSetPreference(
|
|
116
|
+
args: SetPreferenceArgs,
|
|
117
|
+
userId: string
|
|
118
|
+
): Promise<string> {
|
|
119
|
+
try {
|
|
120
|
+
const { preferences } = args;
|
|
121
|
+
|
|
122
|
+
logger.info('Setting preferences', { userId, updates: Object.keys(preferences) });
|
|
123
|
+
|
|
124
|
+
// Update preferences using service layer
|
|
125
|
+
const updatedPreferences = await PreferencesDatabaseService.updatePreferences(
|
|
126
|
+
userId,
|
|
127
|
+
preferences
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const message = formatPreferenceChangeMessage(preferences);
|
|
131
|
+
|
|
132
|
+
const result: SetPreferenceResult = {
|
|
133
|
+
success: true,
|
|
134
|
+
updated_preferences: updatedPreferences,
|
|
135
|
+
message,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
logger.info('Preferences set successfully', { userId });
|
|
139
|
+
|
|
140
|
+
return JSON.stringify(result, null, 2);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('Failed to set preferences:', error);
|
|
143
|
+
throw new Error(`Failed to set preferences: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
}
|
|
145
|
+
}
|