@prmichaelsen/remember-mcp 3.13.0 → 3.14.1
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/CHANGELOG.md +22 -0
- package/agent/milestones/milestone-17-remember-core-migration.md +140 -0
- package/agent/progress.yaml +123 -6
- package/agent/tasks/milestone-17-remember-core-migration/task-193-foundation-setup.md +58 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-194-migrate-relationship-tools.md +47 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-195-migrate-preference-tools.md +34 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-196-migrate-memory-tools.md +46 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-197-migrate-space-confirmation-tools.md +49 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-198-migrate-space-search-moderate.md +46 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-199-migrate-delete-memory.md +43 -0
- package/agent/tasks/milestone-17-remember-core-migration/task-200-code-cleanup-verification.md +52 -0
- package/dist/core-services.d.ts +25 -0
- package/dist/server-factory.js +3208 -3978
- package/dist/server.js +3708 -4474
- package/dist/tools/confirm-publish-moderation.spec.d.ts +3 -2
- package/dist/tools/create-memory.d.ts +1 -1
- package/dist/tools/query-space.d.ts +1 -1
- package/dist/tools/search-space.d.ts +10 -14
- package/jest.config.js +11 -0
- package/package.json +3 -1
- package/src/core-services.ts +50 -0
- package/src/tools/confirm-publish-moderation.spec.ts +120 -176
- package/src/tools/confirm.ts +70 -1035
- package/src/tools/create-memory.ts +16 -67
- package/src/tools/create-relationship.ts +13 -181
- package/src/tools/delete-memory.ts +7 -72
- package/src/tools/delete-relationship.ts +7 -91
- package/src/tools/deny.ts +4 -14
- package/src/tools/find-similar.ts +16 -110
- package/src/tools/get-preferences.ts +3 -8
- package/src/tools/moderate.spec.ts +65 -81
- package/src/tools/moderate.ts +18 -121
- package/src/tools/publish.ts +7 -204
- package/src/tools/query-space.ts +28 -140
- package/src/tools/retract.ts +7 -185
- package/src/tools/revise.ts +4 -136
- package/src/tools/search-relationship.ts +17 -116
- package/src/tools/search-space.ts +58 -304
- package/src/tools/set-preference.ts +3 -8
- package/src/tools/update-memory.ts +22 -190
- package/src/tools/update-relationship.ts +16 -90
- package/src/v2-smoke.e2e.ts +3 -2
- package/dist/collections/composite-ids.d.ts +0 -106
- package/dist/collections/core-infrastructure.spec.d.ts +0 -11
- package/dist/collections/dot-notation.d.ts +0 -106
- package/dist/collections/tracking-arrays.d.ts +0 -176
- package/dist/constants/content-types.d.ts +0 -61
- package/dist/services/confirmation-token.service.d.ts +0 -99
- package/dist/services/confirmation-token.service.spec.d.ts +0 -5
- package/dist/services/preferences-database.service.d.ts +0 -22
- package/dist/services/space-config.service.d.ts +0 -23
- package/dist/services/space-config.service.spec.d.ts +0 -2
- package/src/collections/composite-ids.ts +0 -193
- package/src/collections/core-infrastructure.spec.ts +0 -353
- package/src/collections/dot-notation.ts +0 -212
- package/src/collections/tracking-arrays.ts +0 -298
- package/src/constants/content-types.ts +0 -490
- package/src/services/confirmation-token.service.spec.ts +0 -254
- package/src/services/confirmation-token.service.ts +0 -328
- package/src/services/preferences-database.service.ts +0 -120
- package/src/services/space-config.service.spec.ts +0 -102
- package/src/services/space-config.service.ts +0 -79
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Memory, DeletedFilter } from '../types/memory.js';
|
|
7
|
-
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
|
-
import { logger } from '../utils/logger.js';
|
|
9
7
|
import { handleToolError } from '../utils/error-handler.js';
|
|
10
|
-
import { buildDeletedFilter, combineFiltersWithAnd } from '../utils/weaviate-filters.js';
|
|
11
8
|
import { createDebugLogger } from '../utils/debug.js';
|
|
12
9
|
import type { AuthContext } from '../types/auth.js';
|
|
10
|
+
import { createCoreServices } from '../core-services.js';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Tool definition for remember_find_similar
|
|
@@ -111,122 +109,30 @@ export async function handleFindSimilar(
|
|
|
111
109
|
try {
|
|
112
110
|
debug.info('Tool invoked');
|
|
113
111
|
debug.trace('Arguments', { args });
|
|
114
|
-
logger.info('Finding similar memories', { userId, memoryId: args.memory_id, hasText: !!args.text });
|
|
115
112
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const collection = getMemoryCollection(userId);
|
|
126
|
-
const limit = args.limit ?? 10;
|
|
127
|
-
const minSimilarity = args.min_similarity ?? 0.7;
|
|
128
|
-
|
|
129
|
-
// Build deleted filter
|
|
130
|
-
const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || 'exclude');
|
|
131
|
-
|
|
132
|
-
// Exclude ghost memories by default
|
|
133
|
-
const ghostExclusionFilter = collection.filter.byProperty('content_type').notEqual('ghost');
|
|
134
|
-
|
|
135
|
-
// Combine filters
|
|
136
|
-
const baseFilter = combineFiltersWithAnd([deletedFilter, ghostExclusionFilter].filter(f => f !== null));
|
|
137
|
-
|
|
138
|
-
let results: any;
|
|
139
|
-
|
|
140
|
-
if (args.memory_id) {
|
|
141
|
-
// Find similar to existing memory
|
|
142
|
-
// First get the memory to verify ownership
|
|
143
|
-
const memory = await collection.query.fetchObjectById(args.memory_id, {
|
|
144
|
-
returnProperties: ['user_id', 'doc_type', 'content'],
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (!memory) {
|
|
148
|
-
throw new Error(`Memory not found: ${args.memory_id}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Verify ownership
|
|
152
|
-
if (memory.properties.user_id !== userId) {
|
|
153
|
-
throw new Error('Unauthorized: Cannot access another user\'s memory');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Verify it's a memory
|
|
157
|
-
if (memory.properties.doc_type !== 'memory') {
|
|
158
|
-
throw new Error('Can only find similar memories for memory documents, not relationships');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Find similar using nearObject
|
|
162
|
-
const searchOptions: any = {
|
|
163
|
-
limit: limit + 1, // +1 to exclude the source memory itself
|
|
164
|
-
distance: 1 - minSimilarity, // Convert similarity to distance
|
|
165
|
-
returnMetadata: ['distance'],
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Add filters if present
|
|
169
|
-
if (baseFilter) {
|
|
170
|
-
searchOptions.filters = baseFilter;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
results = await collection.query.nearObject(args.memory_id, searchOptions);
|
|
174
|
-
|
|
175
|
-
// Filter out the source memory
|
|
176
|
-
results.objects = results.objects.filter((obj: any) => obj.uuid !== args.memory_id);
|
|
177
|
-
} else {
|
|
178
|
-
// Find similar to text
|
|
179
|
-
const searchOptions: any = {
|
|
180
|
-
limit: limit,
|
|
181
|
-
distance: 1 - minSimilarity,
|
|
182
|
-
returnMetadata: ['distance'],
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// Add filters if present
|
|
186
|
-
if (baseFilter) {
|
|
187
|
-
searchOptions.filters = baseFilter;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
results = await collection.query.nearText(args.text!, searchOptions);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Filter to only memories (not relationships) unless requested
|
|
194
|
-
if (!args.include_relationships) {
|
|
195
|
-
results.objects = results.objects.filter(
|
|
196
|
-
(obj: any) => obj.properties.doc_type === 'memory'
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Format results with similarity scores
|
|
201
|
-
const similarMemories: SimilarMemory[] = results.objects.map((obj: any) => {
|
|
202
|
-
const similarity = 1 - (obj.metadata?.distance ?? 0); // Convert distance back to similarity
|
|
203
|
-
return {
|
|
204
|
-
id: obj.uuid,
|
|
205
|
-
...obj.properties,
|
|
206
|
-
similarity: Math.max(0, Math.min(1, similarity)), // Clamp to [0, 1]
|
|
207
|
-
};
|
|
113
|
+
const { memory } = createCoreServices(userId);
|
|
114
|
+
const coreResult = await memory.findSimilar({
|
|
115
|
+
memory_id: args.memory_id,
|
|
116
|
+
text: args.text,
|
|
117
|
+
limit: args.limit,
|
|
118
|
+
min_similarity: args.min_similarity,
|
|
119
|
+
include_relationships: args.include_relationships,
|
|
120
|
+
deleted_filter: args.deleted_filter,
|
|
208
121
|
});
|
|
209
122
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const limitedResults = similarMemories.slice(0, limit);
|
|
215
|
-
|
|
216
|
-
logger.info('Similar memories found', {
|
|
217
|
-
userId,
|
|
218
|
-
query: args.memory_id || args.text,
|
|
219
|
-
results: limitedResults.length,
|
|
220
|
-
});
|
|
123
|
+
// Post-filter ghost content (core doesn't exclude ghosts)
|
|
124
|
+
const filteredMemories = coreResult.similar_memories.filter(
|
|
125
|
+
(m: any) => m.content_type !== 'ghost'
|
|
126
|
+
);
|
|
221
127
|
|
|
222
128
|
const result: FindSimilarResult = {
|
|
223
129
|
query: {
|
|
224
130
|
memory_id: args.memory_id,
|
|
225
131
|
text: args.text,
|
|
226
132
|
},
|
|
227
|
-
similar_memories:
|
|
228
|
-
total:
|
|
229
|
-
min_similarity:
|
|
133
|
+
similar_memories: filteredMemories as unknown as SimilarMemory[],
|
|
134
|
+
total: filteredMemories.length,
|
|
135
|
+
min_similarity: args.min_similarity ?? 0.7,
|
|
230
136
|
};
|
|
231
137
|
|
|
232
138
|
return JSON.stringify(result, null, 2);
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Retrieve user preferences with defaults
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { PreferencesDatabaseService } from '../services/preferences-database.service.js';
|
|
7
|
-
import { logger } from '../utils/logger.js';
|
|
8
6
|
import { handleToolError } from '../utils/error-handler.js';
|
|
9
7
|
import { createDebugLogger } from '../utils/debug.js';
|
|
10
8
|
import {
|
|
@@ -14,6 +12,7 @@ import {
|
|
|
14
12
|
getPreferenceDescription,
|
|
15
13
|
} from '../types/preferences.js';
|
|
16
14
|
import type { AuthContext } from '../types/auth.js';
|
|
15
|
+
import { createCoreServices } from '../core-services.js';
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Tool definition for remember_get_preferences
|
|
@@ -74,10 +73,8 @@ export async function handleGetPreferences(
|
|
|
74
73
|
|
|
75
74
|
const { category } = args;
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Get preferences using service layer
|
|
80
|
-
const preferences = await PreferencesDatabaseService.getPreferences(userId);
|
|
76
|
+
const { preferences: preferencesService } = createCoreServices(userId);
|
|
77
|
+
const preferences = await preferencesService.getPreferences(userId);
|
|
81
78
|
|
|
82
79
|
// Check if these are defaults (no created_at means they were just generated)
|
|
83
80
|
const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
|
|
@@ -110,8 +107,6 @@ export async function handleGetPreferences(
|
|
|
110
107
|
message,
|
|
111
108
|
};
|
|
112
109
|
|
|
113
|
-
logger.info('Preferences retrieved successfully', { userId, category, isDefault });
|
|
114
|
-
|
|
115
110
|
return JSON.stringify(response, null, 2);
|
|
116
111
|
} catch (error) {
|
|
117
112
|
debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -7,25 +7,8 @@ import type { AuthContext, GroupPermissions } from '../types/auth.js';
|
|
|
7
7
|
|
|
8
8
|
// ─── Mocks ───────────────────────────────────────────────────
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
jest.mock('../weaviate/client.js', () => ({
|
|
13
|
-
getWeaviateClient: jest.fn(() => ({
|
|
14
|
-
collections: {
|
|
15
|
-
get: jest.fn().mockReturnValue({
|
|
16
|
-
data: { update: jest.fn().mockResolvedValue(undefined) },
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
})),
|
|
20
|
-
fetchMemoryWithAllProperties: jest.fn(),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
jest.mock('../weaviate/space-schema.js', () => ({
|
|
24
|
-
ensurePublicCollection: jest.fn(),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
jest.mock('../utils/logger.js', () => ({
|
|
28
|
-
logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
10
|
+
jest.mock('../core-services.js', () => ({
|
|
11
|
+
createCoreServices: jest.fn(),
|
|
29
12
|
}));
|
|
30
13
|
|
|
31
14
|
jest.mock('../utils/debug.js', () => ({
|
|
@@ -38,21 +21,12 @@ jest.mock('../utils/debug.js', () => ({
|
|
|
38
21
|
})),
|
|
39
22
|
}));
|
|
40
23
|
|
|
41
|
-
jest.mock('../collections/dot-notation.js', () => ({
|
|
42
|
-
CollectionType: { GROUPS: 'groups' },
|
|
43
|
-
getCollectionName: jest.fn((_: string, id: string) => `Memory_groups_${id}`),
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
24
|
jest.mock('../utils/error-handler.js', () => ({
|
|
47
25
|
handleToolError: jest.fn(() => '{"success":false,"error":"internal"}'),
|
|
48
26
|
}));
|
|
49
27
|
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const mockFetchMemory = fetchMemoryWithAllProperties as jest.MockedFunction<any>;
|
|
54
|
-
const mockGetWeaviateClient = getWeaviateClient as jest.MockedFunction<any>;
|
|
55
|
-
const mockEnsurePublicCollection = ensurePublicCollection as jest.MockedFunction<any>;
|
|
28
|
+
import { createCoreServices } from '../core-services.js';
|
|
29
|
+
const mockCreateCoreServices = createCoreServices as jest.MockedFunction<any>;
|
|
56
30
|
|
|
57
31
|
// ─── Helpers ─────────────────────────────────────────────────
|
|
58
32
|
|
|
@@ -95,14 +69,6 @@ function makeNonModeratorAuth(): AuthContext {
|
|
|
95
69
|
};
|
|
96
70
|
}
|
|
97
71
|
|
|
98
|
-
const PUBLISHED_MEMORY = {
|
|
99
|
-
properties: {
|
|
100
|
-
content: 'Published memory',
|
|
101
|
-
moderation_status: 'pending',
|
|
102
|
-
author_id: 'author-1',
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
72
|
// ─── Tests ───────────────────────────────────────────────────
|
|
107
73
|
|
|
108
74
|
describe('moderateTool definition', () => {
|
|
@@ -121,39 +87,28 @@ describe('moderateTool definition', () => {
|
|
|
121
87
|
});
|
|
122
88
|
|
|
123
89
|
describe('handleModerate', () => {
|
|
124
|
-
let
|
|
125
|
-
let spaceUpdate: jest.Mock;
|
|
90
|
+
let mockModerate: jest.Mock;
|
|
126
91
|
|
|
127
92
|
beforeEach(() => {
|
|
128
93
|
jest.clearAllMocks();
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
mockGetWeaviateClient.mockReturnValue({
|
|
134
|
-
collections: {
|
|
135
|
-
get: jest.fn().mockReturnValue({
|
|
136
|
-
data: { update: groupUpdate },
|
|
137
|
-
}),
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
mockEnsurePublicCollection.mockResolvedValue({
|
|
142
|
-
data: { update: spaceUpdate },
|
|
94
|
+
mockModerate = jest.fn();
|
|
95
|
+
mockCreateCoreServices.mockReturnValue({
|
|
96
|
+
space: { moderate: mockModerate },
|
|
143
97
|
});
|
|
144
|
-
|
|
145
|
-
mockFetchMemory.mockResolvedValue(PUBLISHED_MEMORY);
|
|
146
98
|
});
|
|
147
99
|
|
|
148
|
-
it('returns error when
|
|
100
|
+
it('returns error when core throws for missing destination', async () => {
|
|
101
|
+
mockModerate.mockRejectedValue(new Error('Must specify either space_id or group_id'));
|
|
102
|
+
|
|
149
103
|
const result = JSON.parse(
|
|
150
104
|
await handleModerate({ memory_id: 'mem-1', action: 'approve' }, 'mod-1', makeModeratorAuth('g1'))
|
|
151
105
|
);
|
|
152
106
|
expect(result.success).toBe(false);
|
|
153
|
-
expect(result.error).toBe('Missing destination');
|
|
154
107
|
});
|
|
155
108
|
|
|
156
|
-
it('returns
|
|
109
|
+
it('returns error when core throws for non-moderator on group', async () => {
|
|
110
|
+
mockModerate.mockRejectedValue(new Error('Permission denied'));
|
|
111
|
+
|
|
157
112
|
const result = JSON.parse(
|
|
158
113
|
await handleModerate(
|
|
159
114
|
{ memory_id: 'mem-1', action: 'approve', group_id: 'team-1' },
|
|
@@ -162,10 +117,11 @@ describe('handleModerate', () => {
|
|
|
162
117
|
)
|
|
163
118
|
);
|
|
164
119
|
expect(result.success).toBe(false);
|
|
165
|
-
expect(result.error).toBe('Permission denied');
|
|
166
120
|
});
|
|
167
121
|
|
|
168
|
-
it('returns
|
|
122
|
+
it('returns error when core throws for non-moderator on space', async () => {
|
|
123
|
+
mockModerate.mockRejectedValue(new Error('Permission denied'));
|
|
124
|
+
|
|
169
125
|
const result = JSON.parse(
|
|
170
126
|
await handleModerate(
|
|
171
127
|
{ memory_id: 'mem-1', action: 'approve', space_id: 'public' },
|
|
@@ -174,11 +130,10 @@ describe('handleModerate', () => {
|
|
|
174
130
|
)
|
|
175
131
|
);
|
|
176
132
|
expect(result.success).toBe(false);
|
|
177
|
-
expect(result.error).toBe('Permission denied');
|
|
178
133
|
});
|
|
179
134
|
|
|
180
|
-
it('returns error when memory not found', async () => {
|
|
181
|
-
|
|
135
|
+
it('returns error when core throws for memory not found', async () => {
|
|
136
|
+
mockModerate.mockRejectedValue(new Error('Memory not found'));
|
|
182
137
|
|
|
183
138
|
const result = JSON.parse(
|
|
184
139
|
await handleModerate(
|
|
@@ -188,10 +143,18 @@ describe('handleModerate', () => {
|
|
|
188
143
|
)
|
|
189
144
|
);
|
|
190
145
|
expect(result.success).toBe(false);
|
|
191
|
-
expect(result.error).toBe('Memory not found');
|
|
192
146
|
});
|
|
193
147
|
|
|
194
148
|
it('approves a memory in a group', async () => {
|
|
149
|
+
mockModerate.mockResolvedValue({
|
|
150
|
+
memory_id: 'mem-1',
|
|
151
|
+
action: 'approve',
|
|
152
|
+
moderation_status: 'approved',
|
|
153
|
+
moderated_by: 'mod-1',
|
|
154
|
+
moderated_at: new Date().toISOString(),
|
|
155
|
+
location: 'group:g1',
|
|
156
|
+
});
|
|
157
|
+
|
|
195
158
|
const result = JSON.parse(
|
|
196
159
|
await handleModerate(
|
|
197
160
|
{ memory_id: 'mem-1', action: 'approve', group_id: 'g1' },
|
|
@@ -203,17 +166,18 @@ describe('handleModerate', () => {
|
|
|
203
166
|
expect(result.moderation_status).toBe('approved');
|
|
204
167
|
expect(result.moderated_by).toBe('mod-1');
|
|
205
168
|
expect(result.moderated_at).toBeDefined();
|
|
206
|
-
|
|
207
|
-
expect(groupUpdate).toHaveBeenCalledWith({
|
|
208
|
-
id: 'mem-1',
|
|
209
|
-
properties: expect.objectContaining({
|
|
210
|
-
moderation_status: 'approved',
|
|
211
|
-
moderated_by: 'mod-1',
|
|
212
|
-
}),
|
|
213
|
-
});
|
|
214
169
|
});
|
|
215
170
|
|
|
216
171
|
it('rejects a memory in a group', async () => {
|
|
172
|
+
mockModerate.mockResolvedValue({
|
|
173
|
+
memory_id: 'mem-1',
|
|
174
|
+
action: 'reject',
|
|
175
|
+
moderation_status: 'rejected',
|
|
176
|
+
moderated_by: 'mod-1',
|
|
177
|
+
moderated_at: new Date().toISOString(),
|
|
178
|
+
location: 'group:g1',
|
|
179
|
+
});
|
|
180
|
+
|
|
217
181
|
const result = JSON.parse(
|
|
218
182
|
await handleModerate(
|
|
219
183
|
{ memory_id: 'mem-1', action: 'reject', group_id: 'g1', reason: 'Spam' },
|
|
@@ -227,6 +191,15 @@ describe('handleModerate', () => {
|
|
|
227
191
|
});
|
|
228
192
|
|
|
229
193
|
it('removes a memory in a group', async () => {
|
|
194
|
+
mockModerate.mockResolvedValue({
|
|
195
|
+
memory_id: 'mem-1',
|
|
196
|
+
action: 'remove',
|
|
197
|
+
moderation_status: 'removed',
|
|
198
|
+
moderated_by: 'mod-1',
|
|
199
|
+
moderated_at: new Date().toISOString(),
|
|
200
|
+
location: 'group:g1',
|
|
201
|
+
});
|
|
202
|
+
|
|
230
203
|
const result = JSON.parse(
|
|
231
204
|
await handleModerate(
|
|
232
205
|
{ memory_id: 'mem-1', action: 'remove', group_id: 'g1' },
|
|
@@ -239,6 +212,15 @@ describe('handleModerate', () => {
|
|
|
239
212
|
});
|
|
240
213
|
|
|
241
214
|
it('moderates a memory in a space', async () => {
|
|
215
|
+
mockModerate.mockResolvedValue({
|
|
216
|
+
memory_id: 'mem-1',
|
|
217
|
+
action: 'approve',
|
|
218
|
+
moderation_status: 'approved',
|
|
219
|
+
moderated_by: 'mod-1',
|
|
220
|
+
moderated_at: new Date().toISOString(),
|
|
221
|
+
location: 'space:public',
|
|
222
|
+
});
|
|
223
|
+
|
|
242
224
|
const result = JSON.parse(
|
|
243
225
|
await handleModerate(
|
|
244
226
|
{ memory_id: 'mem-1', action: 'approve', space_id: 'public' },
|
|
@@ -249,18 +231,20 @@ describe('handleModerate', () => {
|
|
|
249
231
|
expect(result.success).toBe(true);
|
|
250
232
|
expect(result.moderation_status).toBe('approved');
|
|
251
233
|
expect(result.location).toBe('space:public');
|
|
252
|
-
|
|
253
|
-
expect(spaceUpdate).toHaveBeenCalledWith({
|
|
254
|
-
id: 'mem-1',
|
|
255
|
-
properties: expect.objectContaining({
|
|
256
|
-
moderation_status: 'approved',
|
|
257
|
-
moderated_by: 'mod-1',
|
|
258
|
-
}),
|
|
259
|
-
});
|
|
260
234
|
});
|
|
261
235
|
|
|
262
236
|
it('sets moderated_at to a valid ISO date', async () => {
|
|
263
237
|
const before = new Date().toISOString();
|
|
238
|
+
const moderatedAt = new Date().toISOString();
|
|
239
|
+
|
|
240
|
+
mockModerate.mockResolvedValue({
|
|
241
|
+
memory_id: 'mem-1',
|
|
242
|
+
action: 'approve',
|
|
243
|
+
moderation_status: 'approved',
|
|
244
|
+
moderated_by: 'mod-1',
|
|
245
|
+
moderated_at: moderatedAt,
|
|
246
|
+
location: 'group:g1',
|
|
247
|
+
});
|
|
264
248
|
|
|
265
249
|
const result = JSON.parse(
|
|
266
250
|
await handleModerate(
|
package/src/tools/moderate.ts
CHANGED
|
@@ -6,23 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import { getWeaviateClient, fetchMemoryWithAllProperties } from '../weaviate/client.js';
|
|
10
|
-
import { ensurePublicCollection } from '../weaviate/space-schema.js';
|
|
11
9
|
import { handleToolError } from '../utils/error-handler.js';
|
|
12
10
|
import { createDebugLogger } from '../utils/debug.js';
|
|
13
|
-
import { logger } from '../utils/logger.js';
|
|
14
|
-
import { CollectionType, getCollectionName } from '../collections/dot-notation.js';
|
|
15
11
|
import type { AuthContext } from '../types/auth.js';
|
|
16
|
-
import {
|
|
12
|
+
import { createCoreServices } from '../core-services.js';
|
|
17
13
|
|
|
18
14
|
type ModerationAction = 'approve' | 'reject' | 'remove';
|
|
19
15
|
|
|
20
|
-
const ACTION_TO_STATUS: Record<ModerationAction, string> = {
|
|
21
|
-
approve: 'approved',
|
|
22
|
-
reject: 'rejected',
|
|
23
|
-
remove: 'removed',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
16
|
export const moderateTool: Tool = {
|
|
27
17
|
name: 'remember_moderate',
|
|
28
18
|
description: `Approve, reject, or remove a published memory. Requires moderator permissions (can_moderate).
|
|
@@ -85,121 +75,28 @@ export async function handleModerate(
|
|
|
85
75
|
debug.info('Tool invoked');
|
|
86
76
|
debug.trace('Arguments', { args });
|
|
87
77
|
|
|
88
|
-
const {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
message: 'Must specify either space_id or group_id',
|
|
97
|
-
},
|
|
98
|
-
null,
|
|
99
|
-
2
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Validate action
|
|
104
|
-
if (!ACTION_TO_STATUS[action]) {
|
|
105
|
-
return JSON.stringify(
|
|
106
|
-
{
|
|
107
|
-
success: false,
|
|
108
|
-
error: 'Invalid action',
|
|
109
|
-
message: `Action must be one of: approve, reject, remove`,
|
|
110
|
-
},
|
|
111
|
-
null,
|
|
112
|
-
2
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Permission check
|
|
117
|
-
if (group_id) {
|
|
118
|
-
if (!canModerate(authContext, group_id)) {
|
|
119
|
-
return JSON.stringify(
|
|
120
|
-
{
|
|
121
|
-
success: false,
|
|
122
|
-
error: 'Permission denied',
|
|
123
|
-
message: `Moderator access required for group ${group_id}`,
|
|
124
|
-
},
|
|
125
|
-
null,
|
|
126
|
-
2
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
} else if (space_id) {
|
|
130
|
-
if (!canModerateAny(authContext)) {
|
|
131
|
-
return JSON.stringify(
|
|
132
|
-
{
|
|
133
|
-
success: false,
|
|
134
|
-
error: 'Permission denied',
|
|
135
|
-
message: `Moderator access required to moderate memories in spaces`,
|
|
136
|
-
},
|
|
137
|
-
null,
|
|
138
|
-
2
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Get the collection
|
|
144
|
-
const weaviateClient = getWeaviateClient();
|
|
145
|
-
let collection: any;
|
|
146
|
-
|
|
147
|
-
if (group_id) {
|
|
148
|
-
const collectionName = getCollectionName(CollectionType.GROUPS, group_id);
|
|
149
|
-
collection = weaviateClient.collections.get(collectionName);
|
|
150
|
-
} else {
|
|
151
|
-
collection = await ensurePublicCollection(weaviateClient);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Fetch the memory
|
|
155
|
-
const memory = await fetchMemoryWithAllProperties(collection, memory_id);
|
|
156
|
-
|
|
157
|
-
if (!memory) {
|
|
158
|
-
return JSON.stringify(
|
|
159
|
-
{
|
|
160
|
-
success: false,
|
|
161
|
-
error: 'Memory not found',
|
|
162
|
-
message: `Published memory ${memory_id} not found in ${group_id ? `group ${group_id}` : `space ${space_id}`}`,
|
|
163
|
-
},
|
|
164
|
-
null,
|
|
165
|
-
2
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Update moderation fields
|
|
170
|
-
const newStatus = ACTION_TO_STATUS[action];
|
|
171
|
-
const now = new Date().toISOString();
|
|
172
|
-
|
|
173
|
-
await collection.data.update({
|
|
174
|
-
id: memory_id,
|
|
175
|
-
properties: {
|
|
176
|
-
moderation_status: newStatus,
|
|
177
|
-
moderated_by: userId,
|
|
178
|
-
moderated_at: now,
|
|
78
|
+
const { space } = createCoreServices(userId);
|
|
79
|
+
const result = await space.moderate(
|
|
80
|
+
{
|
|
81
|
+
memory_id: args.memory_id,
|
|
82
|
+
space_id: args.space_id,
|
|
83
|
+
group_id: args.group_id,
|
|
84
|
+
action: args.action as any,
|
|
85
|
+
reason: args.reason,
|
|
179
86
|
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
logger.info('Memory moderated', {
|
|
183
|
-
tool: 'remember_moderate',
|
|
184
|
-
userId,
|
|
185
|
-
memoryId: memory_id,
|
|
186
|
-
action,
|
|
187
|
-
newStatus,
|
|
188
|
-
spaceId: space_id,
|
|
189
|
-
groupId: group_id,
|
|
190
|
-
reason,
|
|
191
|
-
});
|
|
87
|
+
authContext as any
|
|
88
|
+
);
|
|
192
89
|
|
|
193
90
|
return JSON.stringify(
|
|
194
91
|
{
|
|
195
92
|
success: true,
|
|
196
|
-
memory_id,
|
|
197
|
-
action,
|
|
198
|
-
moderation_status:
|
|
199
|
-
moderated_by:
|
|
200
|
-
moderated_at:
|
|
201
|
-
reason: reason || undefined,
|
|
202
|
-
location:
|
|
93
|
+
memory_id: result.memory_id,
|
|
94
|
+
action: result.action,
|
|
95
|
+
moderation_status: result.moderation_status,
|
|
96
|
+
moderated_by: result.moderated_by,
|
|
97
|
+
moderated_at: result.moderated_at,
|
|
98
|
+
reason: args.reason || undefined,
|
|
99
|
+
location: result.location,
|
|
203
100
|
},
|
|
204
101
|
null,
|
|
205
102
|
2
|