@prmichaelsen/remember-mcp 3.0.0 → 3.13.0
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.md +296 -250
- package/CHANGELOG.md +358 -0
- package/README.md +68 -45
- package/agent/commands/acp.clarification-create.md +382 -0
- package/agent/commands/acp.project-info.md +309 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-update.md +296 -0
- package/agent/commands/acp.task-create.md +17 -9
- package/agent/commands/git.commit.md +13 -1
- package/agent/design/comment-memory-type.md +2 -2
- package/agent/design/local.collaborative-memory-sync.md +265 -0
- package/agent/design/local.content-flags.md +210 -0
- package/agent/design/local.ghost-persona-system.md +273 -0
- package/agent/design/local.group-acl-integration.md +338 -0
- package/agent/design/local.memory-acl-schema.md +352 -0
- package/agent/design/local.memory-collection-pattern-v2.md +348 -0
- package/agent/design/local.moderation-and-space-config.md +257 -0
- package/agent/design/local.v2-api-reference.md +621 -0
- package/agent/design/local.v2-migration-guide.md +191 -0
- package/agent/design/local.v2-usage-examples.md +265 -0
- package/agent/design/permissions-storage-architecture.md +11 -3
- package/agent/design/trust-escalation-prevention.md +9 -2
- package/agent/design/trust-system-implementation.md +12 -3
- package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
- package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
- package/agent/progress.yaml +628 -49
- package/agent/scripts/acp.common.sh +2 -0
- package/agent/scripts/acp.install.sh +11 -1
- package/agent/scripts/acp.package-install-optimized.sh +454 -0
- package/agent/scripts/acp.package-install.sh +247 -300
- package/agent/scripts/acp.project-info.sh +218 -0
- package/agent/scripts/acp.project-remove.sh +302 -0
- package/agent/scripts/acp.project-update.sh +296 -0
- package/agent/scripts/acp.yaml-parser.sh +128 -10
- package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
- package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
- package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
- package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
- package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
- package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
- package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
- package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
- package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
- package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
- package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
- package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
- package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
- package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
- package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
- package/dist/collections/composite-ids.d.ts +106 -0
- package/dist/collections/core-infrastructure.spec.d.ts +11 -0
- package/dist/collections/dot-notation.d.ts +106 -0
- package/dist/collections/tracking-arrays.d.ts +176 -0
- package/dist/constants/content-types.d.ts +1 -0
- package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
- package/dist/schema/v2-collections.d.ts +210 -0
- package/dist/server-factory.d.ts +15 -0
- package/dist/server-factory.js +2798 -1029
- package/dist/server.js +2526 -1012
- package/dist/services/access-control.d.ts +103 -0
- package/dist/services/access-control.spec.d.ts +2 -0
- package/dist/services/credentials-provider.d.ts +24 -0
- package/dist/services/credentials-provider.spec.d.ts +2 -0
- package/dist/services/escalation.service.d.ts +22 -0
- package/dist/services/escalation.service.spec.d.ts +2 -0
- package/dist/services/ghost-config.service.d.ts +55 -0
- package/dist/services/ghost-config.service.spec.d.ts +2 -0
- package/dist/services/space-config.service.d.ts +23 -0
- package/dist/services/space-config.service.spec.d.ts +2 -0
- package/dist/services/trust-enforcement.d.ts +83 -0
- package/dist/services/trust-enforcement.spec.d.ts +2 -0
- package/dist/services/trust-validator.d.ts +43 -0
- package/dist/services/trust-validator.spec.d.ts +2 -0
- package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
- package/dist/tools/confirm.d.ts +8 -1
- package/dist/tools/create-memory.d.ts +2 -1
- package/dist/tools/create-memory.spec.d.ts +10 -0
- package/dist/tools/create-relationship.d.ts +2 -1
- package/dist/tools/delete-memory.d.ts +2 -1
- package/dist/tools/delete-relationship.d.ts +2 -1
- package/dist/tools/deny.d.ts +2 -1
- package/dist/tools/find-similar.d.ts +2 -1
- package/dist/tools/get-preferences.d.ts +2 -1
- package/dist/tools/ghost-config.d.ts +27 -0
- package/dist/tools/ghost-config.spec.d.ts +2 -0
- package/dist/tools/moderate.d.ts +20 -0
- package/dist/tools/moderate.spec.d.ts +5 -0
- package/dist/tools/publish.d.ts +11 -3
- package/dist/tools/query-memory.d.ts +3 -1
- package/dist/tools/query-space.d.ts +4 -1
- package/dist/tools/retract.d.ts +29 -0
- package/dist/tools/revise.d.ts +45 -0
- package/dist/tools/revise.spec.d.ts +8 -0
- package/dist/tools/search-memory.d.ts +2 -1
- package/dist/tools/search-relationship.d.ts +2 -1
- package/dist/tools/search-space.d.ts +25 -5
- package/dist/tools/search-space.spec.d.ts +9 -0
- package/dist/tools/set-preference.d.ts +2 -1
- package/dist/tools/update-memory.d.ts +2 -1
- package/dist/tools/update-relationship.d.ts +2 -1
- package/dist/types/access-result.d.ts +48 -0
- package/dist/types/access-result.spec.d.ts +2 -0
- package/dist/types/auth.d.ts +46 -0
- package/dist/types/ghost-config.d.ts +36 -0
- package/dist/types/memory.d.ts +3 -1
- package/dist/types/preferences.d.ts +1 -1
- package/dist/utils/auth-helpers.d.ts +14 -0
- package/dist/utils/auth-helpers.spec.d.ts +2 -0
- package/dist/utils/test-data-generator.d.ts +124 -0
- package/dist/utils/test-data-generator.spec.d.ts +12 -0
- package/dist/v2-performance.e2e.d.ts +17 -0
- package/dist/v2-smoke.e2e.d.ts +14 -0
- package/dist/weaviate/client.d.ts +5 -8
- package/dist/weaviate/space-schema.d.ts +2 -2
- package/docs/performance/v2-benchmarks.md +80 -0
- package/jest.e2e.config.js +14 -3
- package/package.json +1 -1
- package/scripts/.collection-recreation-state.yaml +16 -0
- package/scripts/.gitkeep +5 -0
- package/scripts/README-collection-recreation.md +224 -0
- package/scripts/README.md +51 -0
- package/scripts/backup-collections.ts +543 -0
- package/scripts/delete-collection.ts +137 -0
- package/scripts/migrate-recreate-collections.ts +578 -0
- package/scripts/migrate-v1-to-v2.ts +1094 -0
- package/scripts/package-lock.json +1113 -0
- package/scripts/package.json +27 -0
- package/src/collections/composite-ids.ts +193 -0
- package/src/collections/core-infrastructure.spec.ts +353 -0
- package/src/collections/dot-notation.ts +212 -0
- package/src/collections/tracking-arrays.ts +298 -0
- package/src/constants/content-types.ts +20 -0
- package/src/schema/v2-collections-comments.spec.ts +141 -0
- package/src/schema/v2-collections.ts +433 -0
- package/src/server-factory.ts +89 -20
- package/src/server.ts +45 -17
- package/src/services/access-control.spec.ts +383 -0
- package/src/services/access-control.ts +291 -0
- package/src/services/credentials-provider.spec.ts +22 -0
- package/src/services/credentials-provider.ts +34 -0
- package/src/services/escalation.service.spec.ts +183 -0
- package/src/services/escalation.service.ts +150 -0
- package/src/services/ghost-config.service.spec.ts +339 -0
- package/src/services/ghost-config.service.ts +219 -0
- package/src/services/space-config.service.spec.ts +102 -0
- package/src/services/space-config.service.ts +79 -0
- package/src/services/trust-enforcement.spec.ts +309 -0
- package/src/services/trust-enforcement.ts +197 -0
- package/src/services/trust-validator.spec.ts +108 -0
- package/src/services/trust-validator.ts +105 -0
- package/src/tools/confirm-publish-moderation.spec.ts +240 -0
- package/src/tools/confirm.ts +869 -135
- package/src/tools/create-memory.spec.ts +126 -0
- package/src/tools/create-memory.ts +20 -27
- package/src/tools/create-relationship.ts +17 -8
- package/src/tools/delete-memory.ts +13 -6
- package/src/tools/delete-relationship.ts +15 -6
- package/src/tools/deny.ts +8 -1
- package/src/tools/find-similar.ts +21 -8
- package/src/tools/get-preferences.ts +10 -1
- package/src/tools/ghost-config.spec.ts +180 -0
- package/src/tools/ghost-config.ts +230 -0
- package/src/tools/moderate.spec.ts +277 -0
- package/src/tools/moderate.ts +219 -0
- package/src/tools/publish.ts +99 -41
- package/src/tools/query-memory.ts +28 -6
- package/src/tools/query-space.ts +39 -4
- package/src/tools/retract.ts +292 -0
- package/src/tools/revise.spec.ts +146 -0
- package/src/tools/revise.ts +283 -0
- package/src/tools/search-memory.ts +30 -7
- package/src/tools/search-relationship.ts +11 -2
- package/src/tools/search-space.spec.ts +341 -0
- package/src/tools/search-space.ts +323 -99
- package/src/tools/set-preference.ts +10 -1
- package/src/tools/update-memory.ts +16 -5
- package/src/tools/update-relationship.ts +10 -1
- package/src/types/access-result.spec.ts +193 -0
- package/src/types/access-result.ts +62 -0
- package/src/types/auth.ts +52 -0
- package/src/types/ghost-config.ts +46 -0
- package/src/types/memory.ts +9 -1
- package/src/types/preferences.ts +2 -2
- package/src/utils/auth-helpers.spec.ts +75 -0
- package/src/utils/auth-helpers.ts +25 -0
- package/src/utils/test-data-generator.spec.ts +317 -0
- package/src/utils/test-data-generator.ts +292 -0
- package/src/utils/weaviate-filters.ts +4 -4
- package/src/v2-performance.e2e.ts +173 -0
- package/src/v2-smoke.e2e.ts +401 -0
- package/src/weaviate/client.spec.ts +5 -5
- package/src/weaviate/client.ts +51 -36
- package/src/weaviate/schema.ts +11 -256
- package/src/weaviate/space-schema.spec.ts +24 -24
- package/src/weaviate/space-schema.ts +18 -6
package/src/tools/confirm.ts
CHANGED
|
@@ -3,15 +3,27 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generic confirmation tool that executes any pending action.
|
|
5
5
|
* This is the second phase of the confirmation workflow.
|
|
6
|
+
*
|
|
7
|
+
* Memory Collection Pattern v2:
|
|
8
|
+
* - Multi-space publication to Memory_spaces_public
|
|
9
|
+
* - Multi-group publication to Memory_groups_{groupId}
|
|
10
|
+
* - Composite IDs ({userId}.{memoryId}) for published memories
|
|
11
|
+
* - Tracking arrays (space_ids, group_ids) on source and published memories
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
15
|
import { confirmationTokenService, type ConfirmationRequest } from '../services/confirmation-token.service.js';
|
|
10
|
-
import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllProperties
|
|
16
|
+
import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllProperties } from '../weaviate/client.js';
|
|
11
17
|
import { ensurePublicCollection } from '../weaviate/space-schema.js';
|
|
12
18
|
import { handleToolError } from '../utils/error-handler.js';
|
|
13
19
|
import { logger } from '../utils/logger.js';
|
|
14
20
|
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
|
+
import type { AuthContext } from '../types/auth.js';
|
|
26
|
+
import { getSpaceConfig } from '../services/space-config.service.js';
|
|
15
27
|
|
|
16
28
|
/**
|
|
17
29
|
* Tool definition for remember_confirm
|
|
@@ -62,7 +74,8 @@ interface ConfirmArgs {
|
|
|
62
74
|
*/
|
|
63
75
|
export async function handleConfirm(
|
|
64
76
|
args: ConfirmArgs,
|
|
65
|
-
userId: string
|
|
77
|
+
userId: string,
|
|
78
|
+
authContext?: AuthContext
|
|
66
79
|
): Promise<string> {
|
|
67
80
|
const debug = createDebugLogger({
|
|
68
81
|
tool: 'remember_confirm',
|
|
@@ -125,10 +138,15 @@ export async function handleConfirm(
|
|
|
125
138
|
return await executeDeleteMemory(request, userId);
|
|
126
139
|
}
|
|
127
140
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
132
150
|
|
|
133
151
|
throw new Error(`Unknown action type: ${request.action}`);
|
|
134
152
|
} catch (error) {
|
|
@@ -147,10 +165,17 @@ export async function handleConfirm(
|
|
|
147
165
|
|
|
148
166
|
/**
|
|
149
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
|
|
150
174
|
*/
|
|
151
175
|
async function executePublishMemory(
|
|
152
176
|
request: ConfirmationRequest & { request_id: string },
|
|
153
|
-
userId: string
|
|
177
|
+
userId: string,
|
|
178
|
+
authContext?: AuthContext
|
|
154
179
|
): Promise<string> {
|
|
155
180
|
const debug = createDebugLogger({
|
|
156
181
|
tool: 'remember_confirm',
|
|
@@ -159,28 +184,47 @@ async function executePublishMemory(
|
|
|
159
184
|
});
|
|
160
185
|
|
|
161
186
|
try {
|
|
187
|
+
// Normalize arrays (handle undefined)
|
|
188
|
+
const spaces = request.payload.spaces || [];
|
|
189
|
+
const groups = request.payload.groups || [];
|
|
190
|
+
|
|
162
191
|
debug.debug('Executing publish memory action', {
|
|
163
192
|
memoryId: request.payload.memory_id,
|
|
164
|
-
spaces
|
|
193
|
+
spaces,
|
|
194
|
+
groups,
|
|
165
195
|
});
|
|
166
196
|
|
|
167
197
|
logger.info('Executing publish memory action', {
|
|
168
198
|
function: 'executePublishMemory',
|
|
169
199
|
userId,
|
|
170
200
|
memoryId: request.payload.memory_id,
|
|
171
|
-
spaces
|
|
172
|
-
|
|
201
|
+
spaces,
|
|
202
|
+
groups,
|
|
203
|
+
spaceCount: spaces.length,
|
|
204
|
+
groupCount: groups.length,
|
|
173
205
|
});
|
|
174
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
|
+
|
|
175
220
|
// Fetch the memory NOW (during confirmation, not from stored payload)
|
|
176
221
|
const weaviateClient = getWeaviateClient();
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
);
|
|
222
|
+
const userCollectionName = getMemoryCollectionName(userId);
|
|
223
|
+
const userCollection = weaviateClient.collections.get(userCollectionName);
|
|
180
224
|
|
|
181
225
|
logger.debug('Fetching original memory', {
|
|
182
226
|
function: 'executePublishMemory',
|
|
183
|
-
collectionName:
|
|
227
|
+
collectionName: userCollectionName,
|
|
184
228
|
memoryId: request.payload.memory_id,
|
|
185
229
|
});
|
|
186
230
|
|
|
@@ -197,14 +241,6 @@ async function executePublishMemory(
|
|
|
197
241
|
memoryId: request.payload.memory_id,
|
|
198
242
|
hasProperties: !!originalMemory?.properties,
|
|
199
243
|
propertyCount: originalMemory?.properties ? Object.keys(originalMemory.properties).length : 0,
|
|
200
|
-
propertyKeys: originalMemory?.properties ? Object.keys(originalMemory.properties) : [],
|
|
201
|
-
hasTitle: !!originalMemory?.properties?.title,
|
|
202
|
-
hasContent: !!originalMemory?.properties?.content,
|
|
203
|
-
hasUserId: !!originalMemory?.properties?.user_id,
|
|
204
|
-
hasTags: !!originalMemory?.properties?.tags,
|
|
205
|
-
hasWeight: !!originalMemory?.properties?.weight,
|
|
206
|
-
contentLength: originalMemory?.properties?.content?.length || 0,
|
|
207
|
-
titleValue: originalMemory?.properties?.title || 'NO_TITLE',
|
|
208
244
|
});
|
|
209
245
|
|
|
210
246
|
if (!originalMemory) {
|
|
@@ -223,28 +259,6 @@ async function executePublishMemory(
|
|
|
223
259
|
);
|
|
224
260
|
}
|
|
225
261
|
|
|
226
|
-
// Check if memory has already been published
|
|
227
|
-
if (originalMemory.properties.space_memory_id) {
|
|
228
|
-
const requestedSpaces = request.payload.spaces?.join(', ') || 'unknown';
|
|
229
|
-
logger.warn('Memory already published', {
|
|
230
|
-
function: 'executePublishMemory',
|
|
231
|
-
memoryId: request.payload.memory_id,
|
|
232
|
-
existingSpaceMemoryId: originalMemory.properties.space_memory_id,
|
|
233
|
-
requestedSpaces: request.payload.spaces,
|
|
234
|
-
});
|
|
235
|
-
return JSON.stringify(
|
|
236
|
-
{
|
|
237
|
-
success: false,
|
|
238
|
-
error: 'Already published',
|
|
239
|
-
message: `This memory has already been published to this space. Space memory ID: ${originalMemory.properties.space_memory_id}`,
|
|
240
|
-
space_memory_id: originalMemory.properties.space_memory_id,
|
|
241
|
-
requested_spaces: request.payload.spaces,
|
|
242
|
-
},
|
|
243
|
-
null,
|
|
244
|
-
2
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
262
|
// Verify ownership again
|
|
249
263
|
if (originalMemory.properties.user_id !== userId) {
|
|
250
264
|
logger.warn('Permission denied - wrong owner', {
|
|
@@ -263,115 +277,300 @@ async function executePublishMemory(
|
|
|
263
277
|
2
|
|
264
278
|
);
|
|
265
279
|
}
|
|
266
|
-
|
|
267
|
-
logger.debug('Ensuring public collection exists', {
|
|
268
|
-
function: 'executePublishMemory',
|
|
269
|
-
});
|
|
270
280
|
|
|
271
|
-
//
|
|
272
|
-
const
|
|
281
|
+
// Generate composite ID for published memories
|
|
282
|
+
const compositeId = generateCompositeId(userId, request.payload.memory_id);
|
|
273
283
|
|
|
274
|
-
logger.debug('
|
|
284
|
+
logger.debug('Generated composite ID', {
|
|
275
285
|
function: 'executePublishMemory',
|
|
276
|
-
|
|
286
|
+
compositeId,
|
|
287
|
+
userId,
|
|
288
|
+
memoryId: request.payload.memory_id,
|
|
277
289
|
});
|
|
278
|
-
|
|
279
|
-
//
|
|
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
|
|
280
300
|
const originalTags = Array.isArray(originalMemory.properties.tags)
|
|
281
301
|
? originalMemory.properties.tags
|
|
282
302
|
: [];
|
|
283
303
|
const additionalTags = Array.isArray(request.payload.additional_tags)
|
|
284
304
|
? request.payload.additional_tags
|
|
285
305
|
: [];
|
|
286
|
-
|
|
287
|
-
// Validate payload has required fields
|
|
288
|
-
if (!request.payload.spaces || !Array.isArray(request.payload.spaces) || request.payload.spaces.length === 0) {
|
|
289
|
-
throw new Error('Payload missing required field: spaces');
|
|
290
|
-
}
|
|
306
|
+
const mergedTags = [...originalTags, ...additionalTags];
|
|
291
307
|
|
|
292
|
-
//
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
author_id: userId, // Track original author
|
|
298
|
-
published_at: new Date().toISOString(),
|
|
299
|
-
discovery_count: 0,
|
|
300
|
-
attribution: 'user' as const,
|
|
301
|
-
// Merge additional tags with original tags
|
|
302
|
-
tags: [...originalTags, ...additionalTags],
|
|
303
|
-
// Keep doc_type as 'memory' (space_memory concept was removed)
|
|
304
|
-
// Keep original created_at, updated_at, version (don't overwrite)
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
logger.info('Inserting memory into Memory_public', {
|
|
308
|
-
function: 'executePublishMemory',
|
|
309
|
-
spaces: request.payload.spaces,
|
|
310
|
-
spaceCount: request.payload.spaces?.length || 0,
|
|
311
|
-
memoryId: request.payload.memory_id,
|
|
312
|
-
hasUserId: !!(publishedMemory as any).user_id,
|
|
313
|
-
hasAuthorId: !!publishedMemory.author_id,
|
|
314
|
-
publishedMemoryKeys: Object.keys(publishedMemory),
|
|
315
|
-
publishedMemoryKeyCount: Object.keys(publishedMemory).length,
|
|
316
|
-
hasContent: !!publishedMemory.content,
|
|
317
|
-
hasTitle: !!publishedMemory.title,
|
|
318
|
-
contentLength: publishedMemory.content?.length || 0,
|
|
319
|
-
titleValue: publishedMemory.title || 'NO_TITLE',
|
|
320
|
-
});
|
|
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: [] };
|
|
321
313
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
properties: publishedMemory,
|
|
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',
|
|
327
318
|
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
logger.info('Memory published successfully', {
|
|
331
|
-
function: 'executePublishMemory',
|
|
332
|
-
spaceMemoryId: result,
|
|
333
|
-
spaces: request.payload.spaces,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
debug.info('Memory published successfully', {
|
|
337
|
-
spaceMemoryId: result,
|
|
338
|
-
spaces: request.payload.spaces,
|
|
339
|
-
});
|
|
340
319
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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;
|
|
349
366
|
|
|
350
|
-
logger.info('
|
|
367
|
+
logger.info('Publishing memory to Memory_spaces_public', {
|
|
351
368
|
function: 'executePublishMemory',
|
|
352
|
-
|
|
353
|
-
|
|
369
|
+
compositeId,
|
|
370
|
+
spaces,
|
|
371
|
+
spaceIds: newSpaceIds,
|
|
354
372
|
});
|
|
355
|
-
|
|
356
|
-
|
|
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', {
|
|
357
413
|
function: 'executePublishMemory',
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
error: updateError instanceof Error ? updateError.message : String(updateError),
|
|
414
|
+
groupId,
|
|
415
|
+
collectionName: groupCollectionName,
|
|
361
416
|
});
|
|
362
|
-
// Don't fail the publish if this update fails - it's not critical
|
|
363
|
-
}
|
|
364
417
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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,
|
|
509
|
+
},
|
|
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
|
+
}
|
|
375
574
|
} catch (error) {
|
|
376
575
|
debug.error('Execute publish failed', {
|
|
377
576
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -391,7 +590,8 @@ async function executePublishMemory(
|
|
|
391
590
|
*/
|
|
392
591
|
async function executeDeleteMemory(
|
|
393
592
|
request: ConfirmationRequest & { request_id: string },
|
|
394
|
-
userId: string
|
|
593
|
+
userId: string,
|
|
594
|
+
authContext?: AuthContext
|
|
395
595
|
): Promise<string> {
|
|
396
596
|
try {
|
|
397
597
|
logger.info('Executing delete memory action', {
|
|
@@ -405,7 +605,7 @@ async function executeDeleteMemory(
|
|
|
405
605
|
|
|
406
606
|
// Soft delete the memory
|
|
407
607
|
const client = getWeaviateClient();
|
|
408
|
-
const collectionName =
|
|
608
|
+
const collectionName = getMemoryCollectionName(userId);
|
|
409
609
|
const collection = client.collections.get(collectionName);
|
|
410
610
|
|
|
411
611
|
await collection.data.update({
|
|
@@ -444,3 +644,537 @@ async function executeDeleteMemory(
|
|
|
444
644
|
throw error;
|
|
445
645
|
}
|
|
446
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)
|
|
738
|
+
);
|
|
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
|
+
}
|
|
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
|
+
|
|
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
|
+
});
|
|
818
|
+
|
|
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
|
+
}
|
|
854
|
+
|
|
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,
|
|
869
|
+
properties: {
|
|
870
|
+
space_ids: finalSpaceIds,
|
|
871
|
+
group_ids: finalGroupIds,
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
|
|
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
|
+
return JSON.stringify(
|
|
913
|
+
{
|
|
914
|
+
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,
|
|
932
|
+
},
|
|
933
|
+
null,
|
|
934
|
+
2
|
|
935
|
+
);
|
|
936
|
+
}
|
|
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
|
+
|
|
988
|
+
// Fetch source memory
|
|
989
|
+
const weaviateClient = getWeaviateClient();
|
|
990
|
+
const userCollectionName = getMemoryCollectionName(userId);
|
|
991
|
+
const userCollection = weaviateClient.collections.get(userCollectionName);
|
|
992
|
+
|
|
993
|
+
const sourceMemory = await fetchMemoryWithAllProperties(
|
|
994
|
+
userCollection,
|
|
995
|
+
memory_id
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
if (!sourceMemory) {
|
|
999
|
+
return JSON.stringify(
|
|
1000
|
+
{
|
|
1001
|
+
success: false,
|
|
1002
|
+
error: 'Memory not found',
|
|
1003
|
+
message: `Source memory ${memory_id} no longer exists`,
|
|
1004
|
+
},
|
|
1005
|
+
null,
|
|
1006
|
+
2
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Verify ownership again
|
|
1011
|
+
if (sourceMemory.properties.user_id !== userId) {
|
|
1012
|
+
return JSON.stringify(
|
|
1013
|
+
{
|
|
1014
|
+
success: false,
|
|
1015
|
+
error: 'Permission denied',
|
|
1016
|
+
message: 'You can only revise your own memories',
|
|
1017
|
+
},
|
|
1018
|
+
null,
|
|
1019
|
+
2
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
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
|
+
|
|
1145
|
+
return JSON.stringify(
|
|
1146
|
+
{
|
|
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
|
+
: {}),
|
|
1164
|
+
},
|
|
1165
|
+
null,
|
|
1166
|
+
2
|
|
1167
|
+
);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
debug.error('Execute revise failed', {
|
|
1170
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1171
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
1172
|
+
});
|
|
1173
|
+
handleToolError(error, {
|
|
1174
|
+
toolName: 'remember_confirm',
|
|
1175
|
+
userId,
|
|
1176
|
+
operation: 'execute revise_memory',
|
|
1177
|
+
action: 'revise_memory',
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|