@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/agent/milestones/milestone-17-remember-core-migration.md +140 -0
  3. package/agent/progress.yaml +123 -6
  4. package/agent/tasks/milestone-17-remember-core-migration/task-193-foundation-setup.md +58 -0
  5. package/agent/tasks/milestone-17-remember-core-migration/task-194-migrate-relationship-tools.md +47 -0
  6. package/agent/tasks/milestone-17-remember-core-migration/task-195-migrate-preference-tools.md +34 -0
  7. package/agent/tasks/milestone-17-remember-core-migration/task-196-migrate-memory-tools.md +46 -0
  8. package/agent/tasks/milestone-17-remember-core-migration/task-197-migrate-space-confirmation-tools.md +49 -0
  9. package/agent/tasks/milestone-17-remember-core-migration/task-198-migrate-space-search-moderate.md +46 -0
  10. package/agent/tasks/milestone-17-remember-core-migration/task-199-migrate-delete-memory.md +43 -0
  11. package/agent/tasks/milestone-17-remember-core-migration/task-200-code-cleanup-verification.md +52 -0
  12. package/dist/core-services.d.ts +25 -0
  13. package/dist/server-factory.js +3208 -3978
  14. package/dist/server.js +3708 -4474
  15. package/dist/tools/confirm-publish-moderation.spec.d.ts +3 -2
  16. package/dist/tools/create-memory.d.ts +1 -1
  17. package/dist/tools/query-space.d.ts +1 -1
  18. package/dist/tools/search-space.d.ts +10 -14
  19. package/jest.config.js +11 -0
  20. package/package.json +3 -1
  21. package/src/core-services.ts +50 -0
  22. package/src/tools/confirm-publish-moderation.spec.ts +120 -176
  23. package/src/tools/confirm.ts +70 -1035
  24. package/src/tools/create-memory.ts +16 -67
  25. package/src/tools/create-relationship.ts +13 -181
  26. package/src/tools/delete-memory.ts +7 -72
  27. package/src/tools/delete-relationship.ts +7 -91
  28. package/src/tools/deny.ts +4 -14
  29. package/src/tools/find-similar.ts +16 -110
  30. package/src/tools/get-preferences.ts +3 -8
  31. package/src/tools/moderate.spec.ts +65 -81
  32. package/src/tools/moderate.ts +18 -121
  33. package/src/tools/publish.ts +7 -204
  34. package/src/tools/query-space.ts +28 -140
  35. package/src/tools/retract.ts +7 -185
  36. package/src/tools/revise.ts +4 -136
  37. package/src/tools/search-relationship.ts +17 -116
  38. package/src/tools/search-space.ts +58 -304
  39. package/src/tools/set-preference.ts +3 -8
  40. package/src/tools/update-memory.ts +22 -190
  41. package/src/tools/update-relationship.ts +16 -90
  42. package/src/v2-smoke.e2e.ts +3 -2
  43. package/dist/collections/composite-ids.d.ts +0 -106
  44. package/dist/collections/core-infrastructure.spec.d.ts +0 -11
  45. package/dist/collections/dot-notation.d.ts +0 -106
  46. package/dist/collections/tracking-arrays.d.ts +0 -176
  47. package/dist/constants/content-types.d.ts +0 -61
  48. package/dist/services/confirmation-token.service.d.ts +0 -99
  49. package/dist/services/confirmation-token.service.spec.d.ts +0 -5
  50. package/dist/services/preferences-database.service.d.ts +0 -22
  51. package/dist/services/space-config.service.d.ts +0 -23
  52. package/dist/services/space-config.service.spec.d.ts +0 -2
  53. package/src/collections/composite-ids.ts +0 -193
  54. package/src/collections/core-infrastructure.spec.ts +0 -353
  55. package/src/collections/dot-notation.ts +0 -212
  56. package/src/collections/tracking-arrays.ts +0 -298
  57. package/src/constants/content-types.ts +0 -490
  58. package/src/services/confirmation-token.service.spec.ts +0 -254
  59. package/src/services/confirmation-token.service.ts +0 -328
  60. package/src/services/preferences-database.service.ts +0 -120
  61. package/src/services/space-config.service.spec.ts +0 -102
  62. package/src/services/space-config.service.ts +0 -79
@@ -12,18 +12,11 @@
12
12
  */
13
13
 
14
14
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
15
- import { confirmationTokenService, type ConfirmationRequest } from '../services/confirmation-token.service.js';
16
- import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllProperties } from '../weaviate/client.js';
17
- import { ensurePublicCollection } from '../weaviate/space-schema.js';
15
+ import { getWeaviateClient, getMemoryCollectionName } from '../weaviate/client.js';
18
16
  import { handleToolError } from '../utils/error-handler.js';
19
- import { logger } from '../utils/logger.js';
20
17
  import { createDebugLogger } from '../utils/debug.js';
21
- import { CollectionType, getCollectionName } from '../collections/dot-notation.js';
22
- import { generateCompositeId, parseCompositeId } from '../collections/composite-ids.js';
23
- import { addToSpaceIds, addToGroupIds, removeFromSpaceIds, removeFromGroupIds, getPublishedLocations } from '../collections/tracking-arrays.js';
24
- import { parseRevisionHistory, buildRevisionHistory, type RevisionResult } from './revise.js';
25
18
  import type { AuthContext } from '../types/auth.js';
26
- import { getSpaceConfig } from '../services/space-config.service.js';
19
+ import { createCoreServices } from '../core-services.js';
27
20
 
28
21
  /**
29
22
  * Tool definition for remember_confirm
@@ -86,30 +79,13 @@ export async function handleConfirm(
86
79
  try {
87
80
  debug.info('Tool invoked');
88
81
  debug.trace('Arguments', { token: args.token });
89
-
90
- logger.info('Starting confirmation', {
91
- tool: 'remember_confirm',
92
- userId,
93
- token: args.token,
94
- });
95
-
96
- // Validate and confirm token
97
- debug.debug('Validating confirmation token');
98
- const request = await debug.time('Confirm token', async () => {
99
- return await confirmationTokenService.confirmRequest(userId, args.token);
100
- });
101
-
102
- logger.debug('Token validation result', {
103
- tool: 'remember_confirm',
104
- requestFound: !!request,
105
- action: request?.action,
106
- });
82
+
83
+ const { space, token: tokenService } = createCoreServices(userId);
84
+
85
+ // Peek at token to determine action type without consuming it
86
+ const request = await tokenService.validateToken(userId, args.token);
107
87
 
108
88
  if (!request) {
109
- logger.info('Token invalid or expired', {
110
- tool: 'remember_confirm',
111
- userId,
112
- });
113
89
  return JSON.stringify(
114
90
  {
115
91
  success: false,
@@ -121,1060 +97,119 @@ export async function handleConfirm(
121
97
  );
122
98
  }
123
99
 
124
- logger.info('Executing confirmed action', {
125
- tool: 'remember_confirm',
126
- action: request.action,
127
- userId,
128
- });
129
-
130
- // GENERIC: Execute action based on type
131
- // This is where the generic pattern delegates to action-specific executors
132
- if (request.action === 'publish_memory') {
133
- return await executePublishMemory(request, userId);
134
- }
135
-
136
- // Handle delete_memory action
100
+ // Handle delete_memory separately (core's confirm doesn't support it)
137
101
  if (request.action === 'delete_memory') {
138
- return await executeDeleteMemory(request, userId);
139
- }
140
-
141
- // Handle retract_memory action
142
- if (request.action === 'retract_memory') {
143
- return await executeRetractMemory(request, userId);
144
- }
145
-
146
- // Handle revise_memory action
147
- if (request.action === 'revise_memory') {
148
- return await executeReviseMemory(request, userId);
149
- }
150
-
151
- throw new Error(`Unknown action type: ${request.action}`);
152
- } catch (error) {
153
- debug.error('Tool failed', {
154
- error: error instanceof Error ? error.message : String(error),
155
- stack: error instanceof Error ? error.stack : undefined,
156
- });
157
- handleToolError(error, {
158
- toolName: 'remember_confirm',
159
- userId,
160
- operation: 'confirm action',
161
- token: args.token,
162
- });
163
- }
164
- }
165
-
166
- /**
167
- * Execute publish memory action
168
- *
169
- * Memory Collection Pattern v2:
170
- * - Publishes to Memory_spaces_public with composite ID
171
- * - Publishes to Memory_groups_{groupId} for each group
172
- * - Updates tracking arrays on source memory
173
- * - Supports rollback on partial failure
174
- */
175
- async function executePublishMemory(
176
- request: ConfirmationRequest & { request_id: string },
177
- userId: string,
178
- authContext?: AuthContext
179
- ): Promise<string> {
180
- const debug = createDebugLogger({
181
- tool: 'remember_confirm',
182
- userId,
183
- operation: 'execute_publish',
184
- });
185
-
186
- try {
187
- // Normalize arrays (handle undefined)
188
- const spaces = request.payload.spaces || [];
189
- const groups = request.payload.groups || [];
190
-
191
- debug.debug('Executing publish memory action', {
192
- memoryId: request.payload.memory_id,
193
- spaces,
194
- groups,
195
- });
196
-
197
- logger.info('Executing publish memory action', {
198
- function: 'executePublishMemory',
199
- userId,
200
- memoryId: request.payload.memory_id,
201
- spaces,
202
- groups,
203
- spaceCount: spaces.length,
204
- groupCount: groups.length,
205
- });
206
-
207
- // Validate that at least one destination is provided
208
- if (spaces.length === 0 && groups.length === 0) {
209
- return JSON.stringify(
210
- {
211
- success: false,
212
- error: 'No destinations',
213
- message: 'Must specify at least one space or group to publish to',
214
- },
215
- null,
216
- 2
217
- );
218
- }
219
-
220
- // Fetch the memory NOW (during confirmation, not from stored payload)
221
- const weaviateClient = getWeaviateClient();
222
- const userCollectionName = getMemoryCollectionName(userId);
223
- const userCollection = weaviateClient.collections.get(userCollectionName);
224
-
225
- logger.debug('Fetching original memory', {
226
- function: 'executePublishMemory',
227
- collectionName: userCollectionName,
228
- memoryId: request.payload.memory_id,
229
- });
230
-
231
- const originalMemory = await debug.time('Fetch original memory', async () => {
232
- return await fetchMemoryWithAllProperties(
233
- userCollection,
234
- request.payload.memory_id
235
- );
236
- });
237
-
238
- logger.info('Original memory fetch result', {
239
- function: 'executePublishMemory',
240
- found: !!originalMemory,
241
- memoryId: request.payload.memory_id,
242
- hasProperties: !!originalMemory?.properties,
243
- propertyCount: originalMemory?.properties ? Object.keys(originalMemory.properties).length : 0,
244
- });
245
-
246
- if (!originalMemory) {
247
- logger.info('Original memory not found', {
248
- function: 'executePublishMemory',
249
- memoryId: request.payload.memory_id,
250
- });
251
- return JSON.stringify(
252
- {
253
- success: false,
254
- error: 'Memory not found',
255
- message: `Original memory ${request.payload.memory_id} no longer exists`,
256
- },
257
- null,
258
- 2
259
- );
260
- }
261
-
262
- // Verify ownership again
263
- if (originalMemory.properties.user_id !== userId) {
264
- logger.warn('Permission denied - wrong owner', {
265
- function: 'executePublishMemory',
266
- memoryId: request.payload.memory_id,
267
- memoryOwner: originalMemory.properties.user_id,
268
- requestingUser: userId,
269
- });
270
- return JSON.stringify(
271
- {
272
- success: false,
273
- error: 'Permission denied',
274
- message: 'You can only publish your own memories',
275
- },
276
- null,
277
- 2
278
- );
279
- }
280
-
281
- // Generate composite ID for published memories
282
- const compositeId = generateCompositeId(userId, request.payload.memory_id);
283
-
284
- logger.debug('Generated composite ID', {
285
- function: 'executePublishMemory',
286
- compositeId,
287
- userId,
288
- memoryId: request.payload.memory_id,
289
- });
290
-
291
- // Get existing tracking arrays from source memory
292
- const existingSpaceIds: string[] = Array.isArray(originalMemory.properties.space_ids)
293
- ? originalMemory.properties.space_ids
294
- : [];
295
- const existingGroupIds: string[] = Array.isArray(originalMemory.properties.group_ids)
296
- ? originalMemory.properties.group_ids
297
- : [];
298
-
299
- // Prepare tags
300
- const originalTags = Array.isArray(originalMemory.properties.tags)
301
- ? originalMemory.properties.tags
302
- : [];
303
- const additionalTags = Array.isArray(request.payload.additional_tags)
304
- ? request.payload.additional_tags
305
- : [];
306
- const mergedTags = [...originalTags, ...additionalTags];
307
-
308
- // Track publication results for rollback
309
- const publicationResults: {
310
- spaces?: { success: boolean; id?: string; error?: string };
311
- groups: Array<{ groupId: string; success: boolean; id?: string; error?: string }>;
312
- } = { groups: [] };
313
-
314
- // STEP 1: Publish to spaces (Memory_spaces_public)
315
- if (spaces.length > 0) {
316
- logger.debug('Ensuring public spaces collection exists', {
317
- function: 'executePublishMemory',
318
- });
319
-
320
- const publicCollection = await ensurePublicCollection(weaviateClient);
321
-
322
- // Check if memory already exists in spaces collection with this composite ID
323
- let existingSpaceMemory = null;
324
- try {
325
- existingSpaceMemory = await fetchMemoryWithAllProperties(publicCollection, compositeId);
326
- } catch (e) {
327
- // Memory doesn't exist, which is fine
328
- }
329
-
330
- // Calculate new space_ids array
331
- const newSpaceIds = [...new Set([...existingSpaceIds, ...spaces])];
332
-
333
- // Determine moderation status: pending if ANY target space requires moderation
334
- let spaceModerationStatus = 'approved';
335
- for (const spaceId of spaces) {
336
- const spaceConfig = await getSpaceConfig(spaceId, 'space');
337
- if (spaceConfig.require_moderation) {
338
- spaceModerationStatus = 'pending';
339
- break;
340
- }
341
- }
342
-
343
- // Create published memory with tracking arrays
344
- const publishedMemory: Record<string, any> = {
345
- ...originalMemory.properties,
346
- // Use composite ID as the document ID
347
- id: compositeId,
348
- // Tracking arrays (v2 feature)
349
- space_ids: newSpaceIds,
350
- group_ids: existingGroupIds,
351
- // Legacy compatibility
352
- spaces: spaces,
353
- // Publication metadata
354
- author_id: userId,
355
- published_at: new Date().toISOString(),
356
- discovery_count: 0,
357
- attribution: 'user' as const,
358
- // Moderation status
359
- moderation_status: spaceModerationStatus,
360
- // Merge tags
361
- tags: mergedTags,
362
- };
363
-
364
- // Remove internal Weaviate properties
365
- delete publishedMemory._additional;
366
-
367
- logger.info('Publishing memory to Memory_spaces_public', {
368
- function: 'executePublishMemory',
369
- compositeId,
370
- spaces,
371
- spaceIds: newSpaceIds,
372
- });
373
-
374
- try {
375
- if (existingSpaceMemory) {
376
- // Update existing memory
377
- await publicCollection.data.update({
378
- id: compositeId,
379
- properties: publishedMemory,
380
- });
381
- publicationResults.spaces = { success: true, id: compositeId };
382
- } else {
383
- // Insert new memory with specific ID
384
- await publicCollection.data.insert({
385
- id: compositeId,
386
- properties: publishedMemory,
387
- });
388
- publicationResults.spaces = { success: true, id: compositeId };
389
- }
390
-
391
- logger.info('Memory published to spaces successfully', {
392
- function: 'executePublishMemory',
393
- compositeId,
394
- spaces,
395
- });
396
- } catch (spaceError) {
397
- logger.error('Failed to publish to spaces', {
398
- function: 'executePublishMemory',
399
- error: spaceError instanceof Error ? spaceError.message : String(spaceError),
400
- });
401
- publicationResults.spaces = {
402
- success: false,
403
- error: spaceError instanceof Error ? spaceError.message : String(spaceError)
404
- };
405
- }
406
- }
407
-
408
- // STEP 2: Publish to groups (Memory_groups_{groupId})
409
- for (const groupId of groups) {
410
- const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
411
-
412
- logger.debug('Publishing to group collection', {
413
- function: 'executePublishMemory',
414
- groupId,
415
- collectionName: groupCollectionName,
416
- });
417
-
418
- try {
419
- const groupCollection = weaviateClient.collections.get(groupCollectionName);
420
-
421
- // Check if memory already exists in this group
422
- let existingGroupMemory = null;
423
- try {
424
- existingGroupMemory = await fetchMemoryWithAllProperties(groupCollection, compositeId);
425
- } catch (e) {
426
- // Memory doesn't exist in this group
427
- }
428
-
429
- // Calculate new group_ids array (for this group publication)
430
- const newGroupIds = [...new Set([...existingGroupIds, groupId])];
431
-
432
- // Determine moderation status for this group
433
- const groupConfig = await getSpaceConfig(groupId, 'group');
434
- const groupModerationStatus = groupConfig.require_moderation ? 'pending' : 'approved';
435
-
436
- // Create published memory for group
437
- const groupMemory: Record<string, any> = {
438
- ...originalMemory.properties,
439
- // Use composite ID
440
- id: compositeId,
441
- // Tracking arrays
442
- space_ids: existingSpaceIds,
443
- group_ids: newGroupIds,
444
- // Publication metadata
445
- author_id: userId,
446
- published_at: new Date().toISOString(),
447
- discovery_count: 0,
448
- attribution: 'user' as const,
449
- // Moderation status
450
- moderation_status: groupModerationStatus,
451
- // Merge tags
452
- tags: mergedTags,
453
- };
454
-
455
- // Remove internal Weaviate properties
456
- delete groupMemory._additional;
457
-
458
- if (existingGroupMemory) {
459
- await groupCollection.data.update({
460
- id: compositeId,
461
- properties: groupMemory,
462
- });
463
- } else {
464
- await groupCollection.data.insert({
465
- id: compositeId,
466
- properties: groupMemory,
467
- });
468
- }
469
-
470
- publicationResults.groups.push({ groupId, success: true, id: compositeId });
471
-
472
- logger.info('Memory published to group successfully', {
473
- function: 'executePublishMemory',
474
- compositeId,
475
- groupId,
476
- });
477
- } catch (groupError) {
478
- logger.error('Failed to publish to group', {
479
- function: 'executePublishMemory',
480
- groupId,
481
- error: groupError instanceof Error ? groupError.message : String(groupError),
482
- });
483
- publicationResults.groups.push({
484
- groupId,
485
- success: false,
486
- error: groupError instanceof Error ? groupError.message : String(groupError)
487
- });
488
- }
489
- }
490
-
491
- // STEP 3: Update source memory with tracking arrays
492
- const finalSpaceIds = publicationResults.spaces?.success
493
- ? [...new Set([...existingSpaceIds, ...spaces])]
494
- : existingSpaceIds;
495
-
496
- const successfulGroups = publicationResults.groups
497
- .filter(g => g.success)
498
- .map(g => g.groupId);
499
- const finalGroupIds = [...new Set([...existingGroupIds, ...successfulGroups])];
500
-
501
- // Only update if there are changes
502
- if (finalSpaceIds.length > existingSpaceIds.length || finalGroupIds.length > existingGroupIds.length) {
503
- try {
504
- await userCollection.data.update({
505
- id: request.payload.memory_id,
506
- properties: {
507
- space_ids: finalSpaceIds,
508
- group_ids: finalGroupIds,
102
+ // Consume the token
103
+ const confirmed = await tokenService.confirmRequest(userId, args.token);
104
+ if (!confirmed) {
105
+ return JSON.stringify(
106
+ {
107
+ success: false,
108
+ error: 'Token already consumed',
109
+ message: 'The confirmation token has already been used.',
509
110
  },
510
- });
511
-
512
- logger.info('Updated source memory with tracking arrays', {
513
- function: 'executePublishMemory',
514
- memoryId: request.payload.memory_id,
515
- spaceIds: finalSpaceIds,
516
- groupIds: finalGroupIds,
517
- });
518
- } catch (updateError) {
519
- logger.warn('Failed to update source memory tracking arrays', {
520
- function: 'executePublishMemory',
521
- memoryId: request.payload.memory_id,
522
- error: updateError instanceof Error ? updateError.message : String(updateError),
523
- });
524
- // Don't fail the publish if this update fails
525
- }
526
- }
527
-
528
- // Build response
529
- const successfulPublications: string[] = [];
530
- const failedPublications: string[] = [];
531
-
532
- if (spaces.length > 0) {
533
- if (publicationResults.spaces?.success) {
534
- successfulPublications.push(`spaces: ${spaces.join(', ')}`);
535
- } else {
536
- failedPublications.push(`spaces: ${publicationResults.spaces?.error || 'unknown error'}`);
537
- }
538
- }
539
-
540
- for (const groupResult of publicationResults.groups) {
541
- if (groupResult.success) {
542
- successfulPublications.push(`group: ${groupResult.groupId}`);
543
- } else {
544
- failedPublications.push(`group ${groupResult.groupId}: ${groupResult.error || 'unknown error'}`);
545
- }
546
- }
547
-
548
- // Return result
549
- if (successfulPublications.length > 0) {
550
- return JSON.stringify(
551
- {
552
- success: true,
553
- composite_id: compositeId,
554
- published_to: successfulPublications,
555
- failed: failedPublications.length > 0 ? failedPublications : undefined,
556
- space_ids: finalSpaceIds,
557
- group_ids: finalGroupIds,
558
- },
559
- null,
560
- 2
561
- );
562
- } else {
563
- return JSON.stringify(
564
- {
565
- success: false,
566
- error: 'Publication failed',
567
- message: 'Failed to publish to any destination',
568
- details: failedPublications,
569
- },
570
- null,
571
- 2
572
- );
573
- }
574
- } catch (error) {
575
- debug.error('Execute publish failed', {
576
- error: error instanceof Error ? error.message : String(error),
577
- stack: error instanceof Error ? error.stack : undefined,
578
- });
579
- handleToolError(error, {
580
- toolName: 'remember_confirm',
581
- userId,
582
- operation: 'execute publish_memory',
583
- action: 'publish_memory',
584
- });
585
- }
586
- }
587
-
588
- /**
589
- * Execute delete memory action
590
- */
591
- async function executeDeleteMemory(
592
- request: ConfirmationRequest & { request_id: string },
593
- userId: string,
594
- authContext?: AuthContext
595
- ): Promise<string> {
596
- try {
597
- logger.info('Executing delete memory action', {
598
- function: 'executeDeleteMemory',
599
- userId,
600
- memoryId: request.payload.memory_id,
601
- hasReason: !!request.payload.reason,
602
- });
603
-
604
- const { memory_id, reason } = request.payload;
605
-
606
- // Soft delete the memory
607
- const client = getWeaviateClient();
608
- const collectionName = getMemoryCollectionName(userId);
609
- const collection = client.collections.get(collectionName);
610
-
611
- await collection.data.update({
612
- id: memory_id,
613
- properties: {
614
- deleted_at: new Date().toISOString(),
615
- deleted_by: userId,
616
- deletion_reason: reason || null,
617
- },
618
- });
619
-
620
- logger.info('Memory soft-deleted successfully', {
621
- function: 'executeDeleteMemory',
622
- userId,
623
- memoryId: memory_id,
624
- deletedAt: new Date().toISOString(),
625
- });
626
-
627
- return JSON.stringify(
628
- {
629
- success: true,
630
- memory_id,
631
- message: 'Memory deleted successfully',
632
- },
633
- null,
634
- 2
635
- );
636
- } catch (error) {
637
- logger.error('Failed to execute delete memory', {
638
- function: 'executeDeleteMemory',
639
- userId,
640
- memoryId: request.payload.memory_id,
641
- error: error instanceof Error ? error.message : String(error),
642
- stack: error instanceof Error ? error.stack : undefined,
643
- });
644
- throw error;
645
- }
646
- }
647
-
648
- /**
649
- * Execute retract memory action
650
- *
651
- * Memory Collection Pattern v2:
652
- * - Selective retraction from specific spaces/groups
653
- * - Space memories are orphaned (remain in Memory_spaces_public with empty tracking arrays)
654
- * - Group memories are deleted from group collections
655
- * - Tracking arrays updated on source memory
656
- */
657
- async function executeRetractMemory(
658
- request: ConfirmationRequest & { request_id: string },
659
- userId: string,
660
- authContext?: AuthContext
661
- ): Promise<string> {
662
- const debug = createDebugLogger({
663
- tool: 'remember_confirm',
664
- userId,
665
- operation: 'execute_retract',
666
- });
667
-
668
- try {
669
- // Normalize arrays (handle undefined)
670
- const spaces = request.payload.spaces || [];
671
- const groups = request.payload.groups || [];
672
-
673
- debug.debug('Executing retract memory action', {
674
- memoryId: request.payload.memory_id,
675
- spaces,
676
- groups,
677
- });
678
-
679
- logger.info('Executing retract memory action', {
680
- function: 'executeRetractMemory',
681
- userId,
682
- memoryId: request.payload.memory_id,
683
- spaces,
684
- groups,
685
- spaceCount: spaces.length,
686
- groupCount: groups.length,
687
- });
688
-
689
- // Fetch the source memory
690
- const weaviateClient = getWeaviateClient();
691
- const userCollectionName = getMemoryCollectionName(userId);
692
- const userCollection = weaviateClient.collections.get(userCollectionName);
693
-
694
- const sourceMemory = await fetchMemoryWithAllProperties(
695
- userCollection,
696
- request.payload.memory_id
697
- );
698
-
699
- if (!sourceMemory) {
700
- logger.info('Source memory not found for retraction', {
701
- function: 'executeRetractMemory',
702
- memoryId: request.payload.memory_id,
703
- });
704
- return JSON.stringify(
705
- {
706
- success: false,
707
- error: 'Memory not found',
708
- message: `Source memory ${request.payload.memory_id} no longer exists`,
709
- },
710
- null,
711
- 2
712
- );
713
- }
714
-
715
- // Get current tracking arrays
716
- const currentSpaceIds: string[] = Array.isArray(sourceMemory.properties.space_ids)
717
- ? sourceMemory.properties.space_ids
718
- : [];
719
- const currentGroupIds: string[] = Array.isArray(sourceMemory.properties.group_ids)
720
- ? sourceMemory.properties.group_ids
721
- : [];
722
-
723
- // Generate composite ID for published memories
724
- const compositeId = generateCompositeId(userId, request.payload.memory_id);
725
-
726
- // Track retraction results
727
- const retractionResults: {
728
- spaces?: { success: boolean; error?: string };
729
- groups: Array<{ groupId: string; success: boolean; error?: string }>;
730
- } = { groups: [] };
731
-
732
- // STEP 1: Retract from spaces (Memory_spaces_public)
733
- // Space memories are ORPHANED, not deleted, for historical reference
734
- if (spaces.length > 0) {
735
- try {
736
- const publicCollection = weaviateClient.collections.get(
737
- getCollectionName(CollectionType.SPACES)
111
+ null,
112
+ 2
738
113
  );
739
-
740
- // Fetch the published memory from spaces
741
- const publishedMemory = await fetchMemoryWithAllProperties(publicCollection, compositeId);
742
-
743
- if (publishedMemory) {
744
- // Calculate new space_ids (remove retracted spaces)
745
- const newSpaceIds = currentSpaceIds.filter(id => !spaces.includes(id));
746
-
747
- // Update the published memory with new tracking arrays
748
- // Memory remains even if space_ids becomes empty (orphaned)
749
- await publicCollection.data.update({
750
- id: compositeId,
751
- properties: {
752
- space_ids: newSpaceIds,
753
- retracted_at: new Date().toISOString(),
754
- },
755
- });
756
-
757
- retractionResults.spaces = { success: true };
758
-
759
- logger.info('Memory retracted from spaces (orphaned)', {
760
- function: 'executeRetractMemory',
761
- compositeId,
762
- retractedSpaces: spaces,
763
- remainingSpaces: newSpaceIds,
764
- isOrphaned: newSpaceIds.length === 0 && currentGroupIds.length === 0,
765
- });
766
- } else {
767
- // Memory not found in spaces collection
768
- retractionResults.spaces = {
769
- success: false,
770
- error: 'Memory not found in spaces collection',
771
- };
772
- logger.warn('Memory not found in spaces collection', {
773
- function: 'executeRetractMemory',
774
- compositeId,
775
- });
776
- }
777
- } catch (spaceError) {
778
- logger.error('Failed to retract from spaces', {
779
- function: 'executeRetractMemory',
780
- error: spaceError instanceof Error ? spaceError.message : String(spaceError),
781
- });
782
- retractionResults.spaces = {
783
- success: false,
784
- error: spaceError instanceof Error ? spaceError.message : String(spaceError),
785
- };
786
114
  }
787
- }
788
-
789
- // STEP 2: Retract from groups (Memory_groups_{groupId})
790
- // Group memories are ORPHANED (same as spaces), not deleted
791
- // This preserves historical data while making it unsearchable by default
792
- for (const groupId of groups) {
793
- const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
794
-
795
- try {
796
- const groupCollection = weaviateClient.collections.get(groupCollectionName);
797
-
798
- // Check if memory exists in this group
799
- const groupMemory = await fetchMemoryWithAllProperties(groupCollection, compositeId);
800
115
 
801
- if (groupMemory) {
802
- // Get current group_ids from the group memory
803
- const groupMemoryGroupIds: string[] = Array.isArray(groupMemory.properties.group_ids)
804
- ? groupMemory.properties.group_ids
805
- : [];
806
-
807
- // Remove this group from the group_ids array
808
- const newGroupIds = groupMemoryGroupIds.filter(id => id !== groupId);
809
-
810
- // Update the memory with new tracking arrays (orphan if empty)
811
- await groupCollection.data.update({
812
- id: compositeId,
813
- properties: {
814
- group_ids: newGroupIds,
815
- retracted_at: new Date().toISOString(),
816
- },
817
- });
116
+ const { memory_id, reason } = confirmed.payload;
818
117
 
819
- retractionResults.groups.push({ groupId, success: true });
820
-
821
- logger.info('Memory retracted from group (orphaned)', {
822
- function: 'executeRetractMemory',
823
- compositeId,
824
- groupId,
825
- remainingGroups: newGroupIds,
826
- isOrphaned: newGroupIds.length === 0,
827
- });
828
- } else {
829
- // Memory not found in this group
830
- retractionResults.groups.push({
831
- groupId,
832
- success: false,
833
- error: 'Memory not found in group',
834
- });
835
- logger.warn('Memory not found in group collection', {
836
- function: 'executeRetractMemory',
837
- compositeId,
838
- groupId,
839
- });
840
- }
841
- } catch (groupError) {
842
- logger.error('Failed to retract from group', {
843
- function: 'executeRetractMemory',
844
- groupId,
845
- error: groupError instanceof Error ? groupError.message : String(groupError),
846
- });
847
- retractionResults.groups.push({
848
- groupId,
849
- success: false,
850
- error: groupError instanceof Error ? groupError.message : String(groupError),
851
- });
852
- }
853
- }
118
+ // Soft delete the memory
119
+ const client = getWeaviateClient();
120
+ const collectionName = getMemoryCollectionName(userId);
121
+ const collection = client.collections.get(collectionName);
854
122
 
855
- // STEP 3: Update source memory tracking arrays
856
- const finalSpaceIds = retractionResults.spaces?.success
857
- ? currentSpaceIds.filter(id => !spaces.includes(id))
858
- : currentSpaceIds;
859
-
860
- const successfulGroupRetractions = retractionResults.groups
861
- .filter(g => g.success)
862
- .map(g => g.groupId);
863
- const finalGroupIds = currentGroupIds.filter(id => !successfulGroupRetractions.includes(id));
864
-
865
- // Update source memory
866
- try {
867
- await userCollection.data.update({
868
- id: request.payload.memory_id,
123
+ await collection.data.update({
124
+ id: memory_id,
869
125
  properties: {
870
- space_ids: finalSpaceIds,
871
- group_ids: finalGroupIds,
126
+ deleted_at: new Date().toISOString(),
127
+ deleted_by: userId,
128
+ deletion_reason: reason || null,
872
129
  },
873
130
  });
874
131
 
875
- logger.info('Updated source memory tracking arrays after retraction', {
876
- function: 'executeRetractMemory',
877
- memoryId: request.payload.memory_id,
878
- spaceIds: finalSpaceIds,
879
- groupIds: finalGroupIds,
880
- });
881
- } catch (updateError) {
882
- logger.warn('Failed to update source memory tracking arrays after retraction', {
883
- function: 'executeRetractMemory',
884
- memoryId: request.payload.memory_id,
885
- error: updateError instanceof Error ? updateError.message : String(updateError),
886
- });
887
- // Don't fail the retraction if this update fails
888
- }
889
-
890
- // Build response
891
- const successfulRetractions: string[] = [];
892
- const failedRetractions: string[] = [];
893
-
894
- if (spaces.length > 0) {
895
- if (retractionResults.spaces?.success) {
896
- successfulRetractions.push(`spaces: ${spaces.join(', ')}`);
897
- } else {
898
- failedRetractions.push(`spaces: ${retractionResults.spaces?.error || 'unknown error'}`);
899
- }
900
- }
901
-
902
- for (const groupResult of retractionResults.groups) {
903
- if (groupResult.success) {
904
- successfulRetractions.push(`group: ${groupResult.groupId}`);
905
- } else {
906
- failedRetractions.push(`group ${groupResult.groupId}: ${groupResult.error || 'unknown error'}`);
907
- }
908
- }
909
-
910
- // Return result
911
- if (successfulRetractions.length > 0) {
912
132
  return JSON.stringify(
913
133
  {
914
134
  success: true,
915
- composite_id: compositeId,
916
- retracted_from: successfulRetractions,
917
- failed: failedRetractions.length > 0 ? failedRetractions : undefined,
918
- space_ids: finalSpaceIds,
919
- group_ids: finalGroupIds,
920
- is_orphaned: finalSpaceIds.length === 0 && finalGroupIds.length === 0,
921
- },
922
- null,
923
- 2
924
- );
925
- } else {
926
- return JSON.stringify(
927
- {
928
- success: false,
929
- error: 'Retraction failed',
930
- message: 'Failed to retract from any destination',
931
- details: failedRetractions,
135
+ memory_id,
136
+ message: 'Memory deleted successfully',
932
137
  },
933
138
  null,
934
139
  2
935
140
  );
936
141
  }
937
- } catch (error) {
938
- debug.error('Execute retract failed', {
939
- error: error instanceof Error ? error.message : String(error),
940
- stack: error instanceof Error ? error.stack : undefined,
941
- });
942
- handleToolError(error, {
943
- toolName: 'remember_confirm',
944
- userId,
945
- operation: 'execute retract_memory',
946
- action: 'retract_memory',
947
- });
948
- }
949
- }
950
-
951
- /**
952
- * Execute revise memory action
953
- *
954
- * Memory Collection Pattern v2:
955
- * - Syncs content from source memory to all published copies
956
- * - Preserves old content in revision_history (max 10 entries)
957
- * - Updates revised_at and revision_count on each copy
958
- * - Supports partial success (some locations may fail)
959
- */
960
- async function executeReviseMemory(
961
- request: ConfirmationRequest & { request_id: string },
962
- userId: string,
963
- authContext?: AuthContext
964
- ): Promise<string> {
965
- const debug = createDebugLogger({
966
- tool: 'remember_confirm',
967
- userId,
968
- operation: 'execute_revise',
969
- });
970
-
971
- try {
972
- const { memory_id, space_ids = [], group_ids = [] } = request.payload;
973
-
974
- debug.debug('Executing revise memory action', {
975
- memoryId: memory_id,
976
- spaceIds: space_ids,
977
- groupIds: group_ids,
978
- });
979
-
980
- logger.info('Executing revise memory action', {
981
- function: 'executeReviseMemory',
982
- userId,
983
- memoryId: memory_id,
984
- spaceCount: space_ids.length,
985
- groupCount: group_ids.length,
986
- });
987
142
 
988
- // Fetch source memory
989
- const weaviateClient = getWeaviateClient();
990
- const userCollectionName = getMemoryCollectionName(userId);
991
- const userCollection = weaviateClient.collections.get(userCollectionName);
143
+ // Delegate publish/retract/revise to core SpaceService
144
+ const result = await space.confirm({ token: args.token });
992
145
 
993
- const sourceMemory = await fetchMemoryWithAllProperties(
994
- userCollection,
995
- memory_id
996
- );
997
-
998
- if (!sourceMemory) {
146
+ // Format response based on action type
147
+ if (result.action === 'retract_memory') {
999
148
  return JSON.stringify(
1000
149
  {
1001
- success: false,
1002
- error: 'Memory not found',
1003
- message: `Source memory ${memory_id} no longer exists`,
150
+ success: result.success,
151
+ composite_id: result.composite_id,
152
+ retracted_from: result.retracted_from,
153
+ failed: result.failed?.length ? result.failed : undefined,
154
+ space_ids: result.space_ids,
155
+ group_ids: result.group_ids,
156
+ is_orphaned: (result.space_ids?.length === 0) && (result.group_ids?.length === 0),
1004
157
  },
1005
158
  null,
1006
159
  2
1007
160
  );
1008
161
  }
1009
162
 
1010
- // Verify ownership again
1011
- if (sourceMemory.properties.user_id !== userId) {
163
+ if (result.action === 'revise_memory') {
164
+ const results = result.results || [];
165
+ const successCount = results.filter((r: any) => r.status === 'success').length;
166
+ const failedCount = results.filter((r: any) => r.status === 'failed').length;
167
+ const skippedCount = results.filter((r: any) => r.status === 'skipped').length;
168
+
1012
169
  return JSON.stringify(
1013
170
  {
1014
- success: false,
1015
- error: 'Permission denied',
1016
- message: 'You can only revise your own memories',
171
+ success: result.success,
172
+ composite_id: result.composite_id,
173
+ revised_at: result.revised_at,
174
+ summary: {
175
+ total: results.length,
176
+ success: successCount,
177
+ failed: failedCount,
178
+ skipped: skippedCount,
179
+ },
180
+ results,
181
+ ...(failedCount > 0
182
+ ? { warnings: [`Failed to revise ${failedCount} of ${results.length} location(s)`] }
183
+ : {}),
1017
184
  },
1018
185
  null,
1019
186
  2
1020
187
  );
1021
188
  }
1022
189
 
1023
- const newContent = String(sourceMemory.properties.content ?? '');
1024
- const revisedAt = new Date().toISOString();
1025
- const compositeId = generateCompositeId(userId, memory_id);
1026
- const results: RevisionResult[] = [];
1027
-
1028
- logger.info('Revising published copies', {
1029
- function: 'executeReviseMemory',
1030
- compositeId,
1031
- spaceCount: space_ids.length > 0 ? 1 : 0,
1032
- groupCount: group_ids.length,
1033
- });
1034
-
1035
- /**
1036
- * Update content + revision tracking in a single collection.
1037
- */
1038
- async function reviseInCollection(
1039
- collectionName: string,
1040
- locationLabel: string
1041
- ): Promise<void> {
1042
- try {
1043
- const collection = weaviateClient.collections.get(collectionName);
1044
- const publishedMemory = await fetchMemoryWithAllProperties(
1045
- collection,
1046
- compositeId
1047
- );
1048
-
1049
- if (!publishedMemory) {
1050
- results.push({
1051
- location: locationLabel,
1052
- status: 'skipped',
1053
- error: 'Published copy not found (may have been deleted)',
1054
- });
1055
- logger.warn('Published copy not found in collection', {
1056
- function: 'executeReviseMemory',
1057
- collectionName,
1058
- compositeId,
1059
- });
1060
- return;
1061
- }
1062
-
1063
- const oldContent = String(publishedMemory.properties.content ?? '');
1064
-
1065
- // Build updated revision history (only if content actually changed)
1066
- let revisionHistory = parseRevisionHistory(
1067
- publishedMemory.properties.revision_history
1068
- );
1069
- if (oldContent !== newContent) {
1070
- revisionHistory = buildRevisionHistory(
1071
- revisionHistory,
1072
- oldContent,
1073
- revisedAt
1074
- );
1075
- }
1076
-
1077
- const currentRevisionCount =
1078
- typeof publishedMemory.properties.revision_count === 'number'
1079
- ? publishedMemory.properties.revision_count
1080
- : 0;
1081
-
1082
- await collection.data.update({
1083
- id: compositeId,
1084
- properties: {
1085
- content: newContent,
1086
- revised_at: revisedAt,
1087
- revision_count: currentRevisionCount + 1,
1088
- revision_history: JSON.stringify(revisionHistory),
1089
- },
1090
- });
1091
-
1092
- results.push({ location: locationLabel, status: 'success' });
1093
-
1094
- logger.info('Revised published memory in collection', {
1095
- function: 'executeReviseMemory',
1096
- collectionName,
1097
- compositeId,
1098
- revisionCount: currentRevisionCount + 1,
1099
- contentChanged: oldContent !== newContent,
1100
- });
1101
- } catch (err) {
1102
- results.push({
1103
- location: locationLabel,
1104
- status: 'failed',
1105
- error: err instanceof Error ? err.message : String(err),
1106
- });
1107
- logger.error('Failed to revise in collection', {
1108
- function: 'executeReviseMemory',
1109
- collectionName,
1110
- compositeId,
1111
- error: err instanceof Error ? err.message : String(err),
1112
- });
1113
- }
1114
- }
1115
-
1116
- // Revise in Memory_spaces_public (single collection for all spaces)
1117
- if (space_ids.length > 0) {
1118
- await reviseInCollection(
1119
- getCollectionName(CollectionType.SPACES),
1120
- 'Memory_spaces_public'
1121
- );
1122
- }
1123
-
1124
- // Revise in each group's collection
1125
- for (const groupId of group_ids) {
1126
- await reviseInCollection(
1127
- getCollectionName(CollectionType.GROUPS, groupId),
1128
- `Memory_groups_${groupId}`
1129
- );
1130
- }
1131
-
1132
- const successCount = results.filter(r => r.status === 'success').length;
1133
- const failedCount = results.filter(r => r.status === 'failed').length;
1134
- const skippedCount = results.filter(r => r.status === 'skipped').length;
1135
-
1136
- logger.info('Revise execution complete', {
1137
- function: 'executeReviseMemory',
1138
- userId,
1139
- memoryId: memory_id,
1140
- successCount,
1141
- failedCount,
1142
- skippedCount,
1143
- });
1144
-
190
+ // Default: publish_memory (and any future action types)
1145
191
  return JSON.stringify(
1146
192
  {
1147
- success: successCount > 0,
1148
- composite_id: compositeId,
1149
- revised_at: revisedAt,
1150
- summary: {
1151
- total: results.length,
1152
- success: successCount,
1153
- failed: failedCount,
1154
- skipped: skippedCount,
1155
- },
1156
- results,
1157
- ...(failedCount > 0
1158
- ? {
1159
- warnings: [
1160
- `Failed to revise ${failedCount} of ${results.length} location(s)`,
1161
- ],
1162
- }
1163
- : {}),
193
+ success: result.success,
194
+ composite_id: result.composite_id,
195
+ published_to: result.published_to,
196
+ failed: result.failed?.length ? result.failed : undefined,
197
+ space_ids: result.space_ids,
198
+ group_ids: result.group_ids,
1164
199
  },
1165
200
  null,
1166
201
  2
1167
202
  );
1168
203
  } catch (error) {
1169
- debug.error('Execute revise failed', {
204
+ debug.error('Tool failed', {
1170
205
  error: error instanceof Error ? error.message : String(error),
1171
206
  stack: error instanceof Error ? error.stack : undefined,
1172
207
  });
1173
208
  handleToolError(error, {
1174
209
  toolName: 'remember_confirm',
1175
210
  userId,
1176
- operation: 'execute revise_memory',
1177
- action: 'revise_memory',
211
+ operation: 'confirm action',
212
+ token: args.token,
1178
213
  });
1179
214
  }
1180
215
  }