@prmichaelsen/remember-mcp 2.8.0 → 3.12.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.
Files changed (250) hide show
  1. package/AGENT.md +296 -250
  2. package/CHANGELOG.md +468 -0
  3. package/README.md +163 -46
  4. package/agent/commands/acp.clarification-create.md +382 -0
  5. package/agent/commands/acp.command-create.md +0 -1
  6. package/agent/commands/acp.design-create.md +0 -1
  7. package/agent/commands/acp.init.md +0 -1
  8. package/agent/commands/acp.package-create.md +0 -1
  9. package/agent/commands/acp.package-info.md +0 -1
  10. package/agent/commands/acp.package-install.md +0 -1
  11. package/agent/commands/acp.package-list.md +0 -1
  12. package/agent/commands/acp.package-publish.md +0 -1
  13. package/agent/commands/acp.package-remove.md +0 -1
  14. package/agent/commands/acp.package-search.md +0 -1
  15. package/agent/commands/acp.package-update.md +0 -1
  16. package/agent/commands/acp.package-validate.md +0 -1
  17. package/agent/commands/acp.pattern-create.md +0 -1
  18. package/agent/commands/acp.plan.md +0 -1
  19. package/agent/commands/acp.proceed.md +0 -1
  20. package/agent/commands/acp.project-create.md +0 -1
  21. package/agent/commands/acp.project-info.md +309 -0
  22. package/agent/commands/acp.project-list.md +0 -1
  23. package/agent/commands/acp.project-remove.md +379 -0
  24. package/agent/commands/acp.project-set.md +0 -1
  25. package/agent/commands/acp.project-update.md +296 -0
  26. package/agent/commands/acp.report.md +0 -1
  27. package/agent/commands/acp.resume.md +0 -1
  28. package/agent/commands/acp.status.md +0 -1
  29. package/agent/commands/acp.sync.md +0 -1
  30. package/agent/commands/acp.task-create.md +17 -10
  31. package/agent/commands/acp.update.md +0 -1
  32. package/agent/commands/acp.validate.md +0 -1
  33. package/agent/commands/acp.version-check-for-updates.md +0 -1
  34. package/agent/commands/acp.version-check.md +0 -1
  35. package/agent/commands/acp.version-update.md +0 -1
  36. package/agent/commands/command.template.md +0 -5
  37. package/agent/commands/git.commit.md +13 -2
  38. package/agent/commands/git.init.md +0 -1
  39. package/agent/design/comment-memory-type.md +2 -2
  40. package/agent/design/local.collaborative-memory-sync.md +265 -0
  41. package/agent/design/local.content-flags.md +210 -0
  42. package/agent/design/local.ghost-persona-system.md +273 -0
  43. package/agent/design/local.group-acl-integration.md +338 -0
  44. package/agent/design/local.memory-acl-schema.md +352 -0
  45. package/agent/design/local.memory-collection-pattern-v2.md +348 -0
  46. package/agent/design/local.moderation-and-space-config.md +257 -0
  47. package/agent/design/local.v2-api-reference.md +621 -0
  48. package/agent/design/local.v2-migration-guide.md +191 -0
  49. package/agent/design/local.v2-usage-examples.md +265 -0
  50. package/agent/design/permissions-storage-architecture.md +11 -3
  51. package/agent/design/soft-delete-system.md +291 -0
  52. package/agent/design/trust-escalation-prevention.md +9 -2
  53. package/agent/design/trust-system-implementation.md +12 -3
  54. package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
  55. package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
  56. package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
  57. package/agent/package.template.yaml +0 -17
  58. package/agent/progress.yaml +762 -49
  59. package/agent/scripts/acp.common.sh +2 -0
  60. package/agent/scripts/acp.install.sh +15 -85
  61. package/agent/scripts/acp.package-install-optimized.sh +454 -0
  62. package/agent/scripts/acp.package-install.sh +248 -380
  63. package/agent/scripts/acp.package-validate.sh +0 -99
  64. package/agent/scripts/acp.project-info.sh +218 -0
  65. package/agent/scripts/acp.project-remove.sh +302 -0
  66. package/agent/scripts/acp.project-update.sh +296 -0
  67. package/agent/scripts/acp.yaml-parser.sh +128 -10
  68. package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
  69. package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
  70. package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
  71. package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
  72. package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
  73. package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
  74. package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
  75. package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
  76. package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
  77. package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
  78. package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
  79. package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
  80. package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
  81. package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
  82. package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
  83. package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
  84. package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
  85. package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
  86. package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
  87. package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
  88. package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
  89. package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
  90. package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
  91. package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
  92. package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
  93. package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
  94. package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
  95. package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
  96. package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
  97. package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
  98. package/agent/tasks/task-73-update-relationship-handling.md +18 -0
  99. package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
  100. package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
  101. package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
  102. package/dist/collections/composite-ids.d.ts +106 -0
  103. package/dist/collections/core-infrastructure.spec.d.ts +11 -0
  104. package/dist/collections/dot-notation.d.ts +106 -0
  105. package/dist/collections/tracking-arrays.d.ts +176 -0
  106. package/dist/constants/content-types.d.ts +1 -0
  107. package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
  108. package/dist/schema/v2-collections.d.ts +210 -0
  109. package/dist/server-factory.d.ts +15 -0
  110. package/dist/server-factory.js +3261 -1316
  111. package/dist/server.js +2926 -1236
  112. package/dist/services/access-control.d.ts +103 -0
  113. package/dist/services/access-control.spec.d.ts +2 -0
  114. package/dist/services/credentials-provider.d.ts +24 -0
  115. package/dist/services/credentials-provider.spec.d.ts +2 -0
  116. package/dist/services/escalation.service.d.ts +22 -0
  117. package/dist/services/escalation.service.spec.d.ts +2 -0
  118. package/dist/services/ghost-config.service.d.ts +55 -0
  119. package/dist/services/ghost-config.service.spec.d.ts +2 -0
  120. package/dist/services/space-config.service.d.ts +23 -0
  121. package/dist/services/space-config.service.spec.d.ts +2 -0
  122. package/dist/services/trust-enforcement.d.ts +83 -0
  123. package/dist/services/trust-enforcement.spec.d.ts +2 -0
  124. package/dist/services/trust-validator.d.ts +43 -0
  125. package/dist/services/trust-validator.spec.d.ts +2 -0
  126. package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
  127. package/dist/tools/confirm.d.ts +8 -1
  128. package/dist/tools/create-memory.d.ts +2 -1
  129. package/dist/tools/create-memory.spec.d.ts +10 -0
  130. package/dist/tools/create-relationship.d.ts +2 -1
  131. package/dist/tools/delete-memory.d.ts +7 -31
  132. package/dist/tools/delete-relationship.d.ts +2 -1
  133. package/dist/tools/deny.d.ts +2 -1
  134. package/dist/tools/find-similar.d.ts +10 -2
  135. package/dist/tools/get-preferences.d.ts +2 -1
  136. package/dist/tools/ghost-config.d.ts +27 -0
  137. package/dist/tools/ghost-config.spec.d.ts +2 -0
  138. package/dist/tools/moderate.d.ts +20 -0
  139. package/dist/tools/moderate.spec.d.ts +5 -0
  140. package/dist/tools/publish.d.ts +11 -3
  141. package/dist/tools/query-memory.d.ts +11 -2
  142. package/dist/tools/query-space.d.ts +4 -1
  143. package/dist/tools/retract.d.ts +29 -0
  144. package/dist/tools/revise.d.ts +45 -0
  145. package/dist/tools/revise.spec.d.ts +8 -0
  146. package/dist/tools/search-memory.d.ts +8 -1
  147. package/dist/tools/search-relationship.d.ts +10 -2
  148. package/dist/tools/search-space.d.ts +25 -5
  149. package/dist/tools/search-space.spec.d.ts +9 -0
  150. package/dist/tools/set-preference.d.ts +2 -1
  151. package/dist/tools/update-memory.d.ts +2 -1
  152. package/dist/tools/update-relationship.d.ts +2 -1
  153. package/dist/types/access-result.d.ts +48 -0
  154. package/dist/types/access-result.spec.d.ts +2 -0
  155. package/dist/types/auth.d.ts +46 -0
  156. package/dist/types/ghost-config.d.ts +36 -0
  157. package/dist/types/memory.d.ts +11 -1
  158. package/dist/types/preferences.d.ts +1 -1
  159. package/dist/types/space-memory.d.ts +3 -0
  160. package/dist/utils/auth-helpers.d.ts +14 -0
  161. package/dist/utils/auth-helpers.spec.d.ts +2 -0
  162. package/dist/utils/test-data-generator.d.ts +124 -0
  163. package/dist/utils/test-data-generator.spec.d.ts +12 -0
  164. package/dist/utils/weaviate-filters.d.ts +19 -0
  165. package/dist/v2-performance.e2e.d.ts +17 -0
  166. package/dist/v2-smoke.e2e.d.ts +14 -0
  167. package/dist/weaviate/client.d.ts +5 -8
  168. package/dist/weaviate/space-schema.d.ts +2 -2
  169. package/docs/performance/v2-benchmarks.md +80 -0
  170. package/jest.e2e.config.js +14 -3
  171. package/package.json +1 -1
  172. package/scripts/.collection-recreation-state.yaml +16 -0
  173. package/scripts/.gitkeep +5 -0
  174. package/scripts/README-collection-recreation.md +224 -0
  175. package/scripts/README.md +51 -0
  176. package/scripts/backup-collections.ts +543 -0
  177. package/scripts/delete-collection.ts +137 -0
  178. package/scripts/migrate-recreate-collections.ts +578 -0
  179. package/scripts/migrate-v1-to-v2.ts +1094 -0
  180. package/scripts/package-lock.json +1113 -0
  181. package/scripts/package.json +27 -0
  182. package/src/collections/composite-ids.ts +193 -0
  183. package/src/collections/core-infrastructure.spec.ts +353 -0
  184. package/src/collections/dot-notation.ts +212 -0
  185. package/src/collections/tracking-arrays.ts +298 -0
  186. package/src/constants/content-types.ts +20 -0
  187. package/src/schema/v2-collections-comments.spec.ts +141 -0
  188. package/src/schema/v2-collections.ts +433 -0
  189. package/src/server-factory.ts +89 -20
  190. package/src/server.ts +45 -17
  191. package/src/services/access-control.spec.ts +383 -0
  192. package/src/services/access-control.ts +291 -0
  193. package/src/services/credentials-provider.spec.ts +22 -0
  194. package/src/services/credentials-provider.ts +34 -0
  195. package/src/services/escalation.service.spec.ts +183 -0
  196. package/src/services/escalation.service.ts +150 -0
  197. package/src/services/ghost-config.service.spec.ts +339 -0
  198. package/src/services/ghost-config.service.ts +219 -0
  199. package/src/services/space-config.service.spec.ts +102 -0
  200. package/src/services/space-config.service.ts +79 -0
  201. package/src/services/trust-enforcement.spec.ts +309 -0
  202. package/src/services/trust-enforcement.ts +197 -0
  203. package/src/services/trust-validator.spec.ts +108 -0
  204. package/src/services/trust-validator.ts +105 -0
  205. package/src/tools/confirm-publish-moderation.spec.ts +240 -0
  206. package/src/tools/confirm.ts +914 -116
  207. package/src/tools/create-memory.spec.ts +126 -0
  208. package/src/tools/create-memory.ts +20 -27
  209. package/src/tools/create-relationship.ts +30 -8
  210. package/src/tools/delete-memory.ts +99 -64
  211. package/src/tools/delete-relationship.ts +15 -6
  212. package/src/tools/deny.ts +8 -1
  213. package/src/tools/find-similar.ts +44 -6
  214. package/src/tools/get-preferences.ts +10 -1
  215. package/src/tools/ghost-config.spec.ts +180 -0
  216. package/src/tools/ghost-config.ts +230 -0
  217. package/src/tools/moderate.spec.ts +277 -0
  218. package/src/tools/moderate.ts +219 -0
  219. package/src/tools/publish.ts +99 -41
  220. package/src/tools/query-memory.ts +44 -9
  221. package/src/tools/query-space.ts +39 -4
  222. package/src/tools/retract.ts +292 -0
  223. package/src/tools/revise.spec.ts +146 -0
  224. package/src/tools/revise.ts +283 -0
  225. package/src/tools/search-memory.ts +46 -10
  226. package/src/tools/search-relationship.ts +30 -7
  227. package/src/tools/search-space.spec.ts +341 -0
  228. package/src/tools/search-space.ts +323 -99
  229. package/src/tools/set-preference.ts +10 -1
  230. package/src/tools/update-memory.ts +24 -5
  231. package/src/tools/update-relationship.ts +10 -1
  232. package/src/types/access-result.spec.ts +193 -0
  233. package/src/types/access-result.ts +62 -0
  234. package/src/types/auth.ts +52 -0
  235. package/src/types/ghost-config.ts +46 -0
  236. package/src/types/memory.ts +20 -1
  237. package/src/types/preferences.ts +2 -2
  238. package/src/types/space-memory.ts +5 -0
  239. package/src/utils/auth-helpers.spec.ts +75 -0
  240. package/src/utils/auth-helpers.ts +25 -0
  241. package/src/utils/test-data-generator.spec.ts +317 -0
  242. package/src/utils/test-data-generator.ts +292 -0
  243. package/src/utils/weaviate-filters.ts +32 -5
  244. package/src/v2-performance.e2e.ts +173 -0
  245. package/src/v2-smoke.e2e.ts +401 -0
  246. package/src/weaviate/client.spec.ts +5 -5
  247. package/src/weaviate/client.ts +55 -35
  248. package/src/weaviate/schema.ts +11 -239
  249. package/src/weaviate/space-schema.spec.ts +28 -25
  250. package/src/weaviate/space-schema.ts +35 -11
@@ -3,6 +3,12 @@
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';
@@ -12,6 +18,12 @@ 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',
@@ -120,10 +133,20 @@ export async function handleConfirm(
120
133
  return await executePublishMemory(request, userId);
121
134
  }
122
135
 
123
- // Add other action types here as needed
124
- // if (request.action === 'retract_memory') {
125
- // return await executeRetractMemory(request, userId);
126
- // }
136
+ // Handle delete_memory action
137
+ 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
+ }
127
150
 
128
151
  throw new Error(`Unknown action type: ${request.action}`);
129
152
  } catch (error) {
@@ -142,10 +165,17 @@ export async function handleConfirm(
142
165
 
143
166
  /**
144
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
145
174
  */
146
175
  async function executePublishMemory(
147
176
  request: ConfirmationRequest & { request_id: string },
148
- userId: string
177
+ userId: string,
178
+ authContext?: AuthContext
149
179
  ): Promise<string> {
150
180
  const debug = createDebugLogger({
151
181
  tool: 'remember_confirm',
@@ -154,28 +184,47 @@ async function executePublishMemory(
154
184
  });
155
185
 
156
186
  try {
187
+ // Normalize arrays (handle undefined)
188
+ const spaces = request.payload.spaces || [];
189
+ const groups = request.payload.groups || [];
190
+
157
191
  debug.debug('Executing publish memory action', {
158
192
  memoryId: request.payload.memory_id,
159
- spaces: request.payload.spaces,
193
+ spaces,
194
+ groups,
160
195
  });
161
196
 
162
197
  logger.info('Executing publish memory action', {
163
198
  function: 'executePublishMemory',
164
199
  userId,
165
200
  memoryId: request.payload.memory_id,
166
- spaces: request.payload.spaces,
167
- spaceCount: request.payload.spaces?.length || 0,
201
+ spaces,
202
+ groups,
203
+ spaceCount: spaces.length,
204
+ groupCount: groups.length,
168
205
  });
169
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
+
170
220
  // Fetch the memory NOW (during confirmation, not from stored payload)
171
221
  const weaviateClient = getWeaviateClient();
172
- const userCollection = weaviateClient.collections.get(
173
- getMemoryCollectionName(userId)
174
- );
222
+ const userCollectionName = getMemoryCollectionName(userId);
223
+ const userCollection = weaviateClient.collections.get(userCollectionName);
175
224
 
176
225
  logger.debug('Fetching original memory', {
177
226
  function: 'executePublishMemory',
178
- collectionName: getMemoryCollectionName(userId),
227
+ collectionName: userCollectionName,
179
228
  memoryId: request.payload.memory_id,
180
229
  });
181
230
 
@@ -192,14 +241,6 @@ async function executePublishMemory(
192
241
  memoryId: request.payload.memory_id,
193
242
  hasProperties: !!originalMemory?.properties,
194
243
  propertyCount: originalMemory?.properties ? Object.keys(originalMemory.properties).length : 0,
195
- propertyKeys: originalMemory?.properties ? Object.keys(originalMemory.properties) : [],
196
- hasTitle: !!originalMemory?.properties?.title,
197
- hasContent: !!originalMemory?.properties?.content,
198
- hasUserId: !!originalMemory?.properties?.user_id,
199
- hasTags: !!originalMemory?.properties?.tags,
200
- hasWeight: !!originalMemory?.properties?.weight,
201
- contentLength: originalMemory?.properties?.content?.length || 0,
202
- titleValue: originalMemory?.properties?.title || 'NO_TITLE',
203
244
  });
204
245
 
205
246
  if (!originalMemory) {
@@ -218,28 +259,6 @@ async function executePublishMemory(
218
259
  );
219
260
  }
220
261
 
221
- // Check if memory has already been published
222
- if (originalMemory.properties.space_memory_id) {
223
- const requestedSpaces = request.payload.spaces?.join(', ') || 'unknown';
224
- logger.warn('Memory already published', {
225
- function: 'executePublishMemory',
226
- memoryId: request.payload.memory_id,
227
- existingSpaceMemoryId: originalMemory.properties.space_memory_id,
228
- requestedSpaces: request.payload.spaces,
229
- });
230
- return JSON.stringify(
231
- {
232
- success: false,
233
- error: 'Already published',
234
- message: `This memory has already been published to this space. Space memory ID: ${originalMemory.properties.space_memory_id}`,
235
- space_memory_id: originalMemory.properties.space_memory_id,
236
- requested_spaces: request.payload.spaces,
237
- },
238
- null,
239
- 2
240
- );
241
- }
242
-
243
262
  // Verify ownership again
244
263
  if (originalMemory.properties.user_id !== userId) {
245
264
  logger.warn('Permission denied - wrong owner', {
@@ -258,125 +277,904 @@ async function executePublishMemory(
258
277
  2
259
278
  );
260
279
  }
261
-
262
- logger.debug('Ensuring public collection exists', {
263
- function: 'executePublishMemory',
264
- });
265
280
 
266
- // Get unified public collection
267
- const publicCollection = await ensurePublicCollection(weaviateClient);
281
+ // Generate composite ID for published memories
282
+ const compositeId = generateCompositeId(userId, request.payload.memory_id);
268
283
 
269
- logger.debug('Public collection ready', {
284
+ logger.debug('Generated composite ID', {
270
285
  function: 'executePublishMemory',
271
- collectionName: 'Memory_public',
286
+ compositeId,
287
+ userId,
288
+ memoryId: request.payload.memory_id,
272
289
  });
273
-
274
- // Create published memory (copy with modifications)
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
275
300
  const originalTags = Array.isArray(originalMemory.properties.tags)
276
301
  ? originalMemory.properties.tags
277
302
  : [];
278
303
  const additionalTags = Array.isArray(request.payload.additional_tags)
279
304
  ? request.payload.additional_tags
280
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])];
281
332
 
282
- // Validate payload has required fields
283
- if (!request.payload.spaces || !Array.isArray(request.payload.spaces) || request.payload.spaces.length === 0) {
284
- throw new Error('Payload missing required field: spaces');
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
+ }
285
406
  }
286
407
 
287
- // Create published memory - preserve ALL original properties
288
- const publishedMemory = {
289
- ...originalMemory.properties,
290
- // Add space-specific fields (don't overwrite existing properties)
291
- spaces: request.payload.spaces, // Required field (validated above)
292
- author_id: userId, // Track original author
293
- published_at: new Date().toISOString(),
294
- discovery_count: 0,
295
- attribution: 'user' as const,
296
- // Merge additional tags with original tags
297
- tags: [...originalTags, ...additionalTags],
298
- // Keep doc_type as 'memory' (space_memory concept was removed)
299
- // Keep original created_at, updated_at, version (don't overwrite)
300
- };
301
-
302
- logger.info('Inserting memory into Memory_public', {
303
- function: 'executePublishMemory',
304
- spaces: request.payload.spaces,
305
- spaceCount: request.payload.spaces?.length || 0,
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,
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
+ }
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,
306
600
  memoryId: request.payload.memory_id,
307
- hasUserId: !!(publishedMemory as any).user_id,
308
- hasAuthorId: !!publishedMemory.author_id,
309
- publishedMemoryKeys: Object.keys(publishedMemory),
310
- publishedMemoryKeyCount: Object.keys(publishedMemory).length,
311
- hasContent: !!publishedMemory.content,
312
- hasTitle: !!publishedMemory.title,
313
- contentLength: publishedMemory.content?.length || 0,
314
- titleValue: publishedMemory.title || 'NO_TITLE',
601
+ hasReason: !!request.payload.reason,
315
602
  });
316
-
317
- // Insert directly into unified public collection
318
- // CRITICAL: Weaviate insert API expects {properties: {...}}, not the properties directly!
319
- const result = await debug.time('Insert into Memory_public', async () => {
320
- return await publicCollection.data.insert({
321
- properties: publishedMemory,
322
- });
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
+ },
323
618
  });
324
-
325
- logger.info('Memory published successfully', {
326
- function: 'executePublishMemory',
327
- spaceMemoryId: result,
328
- spaces: request.payload.spaces,
619
+
620
+ logger.info('Memory soft-deleted successfully', {
621
+ function: 'executeDeleteMemory',
622
+ userId,
623
+ memoryId: memory_id,
624
+ deletedAt: new Date().toISOString(),
329
625
  });
330
-
331
- debug.info('Memory published successfully', {
332
- spaceMemoryId: result,
333
- spaces: request.payload.spaces,
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,
334
687
  });
335
688
 
336
- // Update original memory with space_memory_id for bidirectional linking
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
337
866
  try {
338
867
  await userCollection.data.update({
339
868
  id: request.payload.memory_id,
340
869
  properties: {
341
- space_memory_id: result,
870
+ space_ids: finalSpaceIds,
871
+ group_ids: finalGroupIds,
342
872
  },
343
873
  });
344
-
345
- logger.info('Updated original memory with space_memory_id', {
346
- function: 'executePublishMemory',
874
+
875
+ logger.info('Updated source memory tracking arrays after retraction', {
876
+ function: 'executeRetractMemory',
347
877
  memoryId: request.payload.memory_id,
348
- spaceMemoryId: result,
878
+ spaceIds: finalSpaceIds,
879
+ groupIds: finalGroupIds,
349
880
  });
350
881
  } catch (updateError) {
351
- logger.warn('Failed to update original memory with space_memory_id', {
352
- function: 'executePublishMemory',
882
+ logger.warn('Failed to update source memory tracking arrays after retraction', {
883
+ function: 'executeRetractMemory',
353
884
  memoryId: request.payload.memory_id,
354
- spaceMemoryId: result,
355
885
  error: updateError instanceof Error ? updateError.message : String(updateError),
356
886
  });
357
- // Don't fail the publish if this update fails - it's not critical
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
+ );
358
1021
  }
359
1022
 
360
- // Return minimal response with spaces array
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
+
361
1145
  return JSON.stringify(
362
1146
  {
363
- success: true,
364
- space_memory_id: result,
365
- spaces: request.payload.spaces || ['the_void'],
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
+ : {}),
366
1164
  },
367
1165
  null,
368
1166
  2
369
1167
  );
370
1168
  } catch (error) {
371
- debug.error('Execute publish failed', {
1169
+ debug.error('Execute revise failed', {
372
1170
  error: error instanceof Error ? error.message : String(error),
373
1171
  stack: error instanceof Error ? error.stack : undefined,
374
1172
  });
375
1173
  handleToolError(error, {
376
1174
  toolName: 'remember_confirm',
377
1175
  userId,
378
- operation: 'execute publish_memory',
379
- action: 'publish_memory',
1176
+ operation: 'execute revise_memory',
1177
+ action: 'revise_memory',
380
1178
  });
381
1179
  }
382
1180
  }