@prmichaelsen/remember-mcp 2.8.0 → 3.0.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 (74) hide show
  1. package/AGENT.md +1 -1
  2. package/CHANGELOG.md +130 -0
  3. package/README.md +95 -1
  4. package/agent/commands/acp.command-create.md +0 -1
  5. package/agent/commands/acp.design-create.md +0 -1
  6. package/agent/commands/acp.init.md +0 -1
  7. package/agent/commands/acp.package-create.md +0 -1
  8. package/agent/commands/acp.package-info.md +0 -1
  9. package/agent/commands/acp.package-install.md +0 -1
  10. package/agent/commands/acp.package-list.md +0 -1
  11. package/agent/commands/acp.package-publish.md +0 -1
  12. package/agent/commands/acp.package-remove.md +0 -1
  13. package/agent/commands/acp.package-search.md +0 -1
  14. package/agent/commands/acp.package-update.md +0 -1
  15. package/agent/commands/acp.package-validate.md +0 -1
  16. package/agent/commands/acp.pattern-create.md +0 -1
  17. package/agent/commands/acp.plan.md +0 -1
  18. package/agent/commands/acp.proceed.md +0 -1
  19. package/agent/commands/acp.project-create.md +0 -1
  20. package/agent/commands/acp.project-list.md +0 -1
  21. package/agent/commands/acp.project-set.md +0 -1
  22. package/agent/commands/acp.report.md +0 -1
  23. package/agent/commands/acp.resume.md +0 -1
  24. package/agent/commands/acp.status.md +0 -1
  25. package/agent/commands/acp.sync.md +0 -1
  26. package/agent/commands/acp.task-create.md +0 -1
  27. package/agent/commands/acp.update.md +0 -1
  28. package/agent/commands/acp.validate.md +0 -1
  29. package/agent/commands/acp.version-check-for-updates.md +0 -1
  30. package/agent/commands/acp.version-check.md +0 -1
  31. package/agent/commands/acp.version-update.md +0 -1
  32. package/agent/commands/command.template.md +0 -5
  33. package/agent/commands/git.commit.md +0 -1
  34. package/agent/commands/git.init.md +0 -1
  35. package/agent/design/soft-delete-system.md +291 -0
  36. package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
  37. package/agent/package.template.yaml +0 -17
  38. package/agent/progress.yaml +136 -2
  39. package/agent/scripts/acp.install.sh +4 -84
  40. package/agent/scripts/acp.package-install.sh +33 -112
  41. package/agent/scripts/acp.package-validate.sh +0 -99
  42. package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
  43. package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
  44. package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
  45. package/agent/tasks/task-73-update-relationship-handling.md +18 -0
  46. package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
  47. package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
  48. package/dist/server-factory.js +677 -501
  49. package/dist/server.js +677 -501
  50. package/dist/tools/delete-memory.d.ts +5 -30
  51. package/dist/tools/find-similar.d.ts +8 -1
  52. package/dist/tools/query-memory.d.ts +8 -1
  53. package/dist/tools/search-memory.d.ts +6 -0
  54. package/dist/tools/search-relationship.d.ts +8 -1
  55. package/dist/types/memory.d.ts +8 -0
  56. package/dist/types/space-memory.d.ts +3 -0
  57. package/dist/utils/weaviate-filters.d.ts +19 -0
  58. package/dist/weaviate/client.d.ts +1 -1
  59. package/package.json +1 -1
  60. package/src/tools/confirm.ts +65 -1
  61. package/src/tools/create-relationship.ts +14 -1
  62. package/src/tools/delete-memory.ts +91 -63
  63. package/src/tools/find-similar.ts +30 -5
  64. package/src/tools/query-memory.ts +18 -5
  65. package/src/tools/search-memory.ts +18 -5
  66. package/src/tools/search-relationship.ts +19 -5
  67. package/src/tools/update-memory.ts +8 -0
  68. package/src/types/memory.ts +11 -0
  69. package/src/types/space-memory.ts +5 -0
  70. package/src/utils/weaviate-filters.ts +28 -1
  71. package/src/weaviate/client.ts +5 -0
  72. package/src/weaviate/schema.ts +17 -0
  73. package/src/weaviate/space-schema.spec.ts +5 -2
  74. package/src/weaviate/space-schema.ts +17 -5
package/dist/server.js CHANGED
@@ -790,7 +790,11 @@ var ALL_MEMORY_PROPERTIES = [
790
790
  "attribution",
791
791
  "published_at",
792
792
  "discovery_count",
793
- "space_memory_id"
793
+ "space_memory_id",
794
+ // Soft delete fields
795
+ "deleted_at",
796
+ "deleted_by",
797
+ "deletion_reason"
794
798
  ];
795
799
  async function fetchMemoryWithAllProperties(collection, memoryId) {
796
800
  const debug = createDebugLogger({
@@ -1128,6 +1132,22 @@ async function createMemoryCollection(userId) {
1128
1132
  name: "moderation_flags",
1129
1133
  dataType: "text[]",
1130
1134
  description: 'Per-space moderation flags (format: "{space_id}:{flag_type}")'
1135
+ },
1136
+ // Soft delete fields
1137
+ {
1138
+ name: "deleted_at",
1139
+ dataType: "date",
1140
+ description: "Timestamp when memory was soft-deleted (null = not deleted)"
1141
+ },
1142
+ {
1143
+ name: "deleted_by",
1144
+ dataType: "text",
1145
+ description: "User ID who deleted the memory"
1146
+ },
1147
+ {
1148
+ name: "deletion_reason",
1149
+ dataType: "text",
1150
+ description: "Optional reason for deletion"
1131
1151
  }
1132
1152
  ]
1133
1153
  });
@@ -1831,6 +1851,14 @@ function combineFiltersWithOr(filters) {
1831
1851
  }
1832
1852
  return Filters.or(...validFilters);
1833
1853
  }
1854
+ function buildDeletedFilter(collection, deletedFilter = "exclude") {
1855
+ if (deletedFilter === "exclude") {
1856
+ return collection.filter.byProperty("deleted_at").isNull(true);
1857
+ } else if (deletedFilter === "only") {
1858
+ return collection.filter.byProperty("deleted_at").isNull(false);
1859
+ }
1860
+ return null;
1861
+ }
1834
1862
 
1835
1863
  // src/tools/search-memory.ts
1836
1864
  var searchMemoryTool = {
@@ -1931,6 +1959,12 @@ var searchMemoryTool = {
1931
1959
  type: "boolean",
1932
1960
  description: "Include relationships in results. Default: true (searches both memories and relationships)",
1933
1961
  default: true
1962
+ },
1963
+ deleted_filter: {
1964
+ type: "string",
1965
+ enum: ["exclude", "include", "only"],
1966
+ default: "exclude",
1967
+ description: 'Filter deleted memories: "exclude" (default, hide deleted), "include" (show all), "only" (show only deleted)'
1934
1968
  }
1935
1969
  },
1936
1970
  required: ["query"]
@@ -1951,19 +1985,22 @@ async function handleSearchMemory(args, userId) {
1951
1985
  const alpha = args.alpha ?? 0.7;
1952
1986
  const limit = args.limit ?? 10;
1953
1987
  const offset = args.offset ?? 0;
1954
- const filters = includeRelationships ? buildCombinedSearchFilters(collection, args.filters) : buildMemoryOnlyFilters(collection, args.filters);
1988
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
1989
+ const searchFilters = includeRelationships ? buildCombinedSearchFilters(collection, args.filters) : buildMemoryOnlyFilters(collection, args.filters);
1990
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, searchFilters].filter((f) => f !== null));
1955
1991
  const searchOptions = {
1956
1992
  alpha,
1957
1993
  limit: limit + offset
1958
1994
  // Get extra for offset
1959
1995
  };
1960
- if (filters) {
1961
- searchOptions.filters = filters;
1996
+ if (combinedFilters) {
1997
+ searchOptions.filters = combinedFilters;
1962
1998
  }
1963
1999
  logger.info("Weaviate query", {
1964
2000
  query: args.query,
1965
2001
  searchOptions: JSON.stringify(searchOptions, null, 2),
1966
- hasFilters: !!filters
2002
+ hasFilters: !!combinedFilters,
2003
+ deletedFilter: args.deleted_filter || "exclude"
1967
2004
  });
1968
2005
  const results = await collection.query.hybrid(args.query, searchOptions);
1969
2006
  const paginatedResults = results.objects.slice(offset);
@@ -2007,142 +2044,407 @@ async function handleSearchMemory(args, userId) {
2007
2044
  }
2008
2045
 
2009
2046
  // src/tools/delete-memory.ts
2047
+ import { Filters as Filters2 } from "weaviate-client";
2048
+
2049
+ // src/services/confirmation-token.service.ts
2050
+ import { randomUUID } from "crypto";
2010
2051
  init_logger();
2011
- var deleteMemoryTool = {
2012
- name: "remember_delete_memory",
2013
- description: `Delete a memory from your collection.
2014
-
2015
- Optionally delete connected relationships as well.
2016
- This action cannot be undone.
2017
-
2018
- Examples:
2019
- - "Delete that old camping note"
2020
- - "Remove the recipe I saved yesterday"
2021
- `,
2022
- inputSchema: {
2023
- type: "object",
2024
- properties: {
2025
- memory_id: {
2026
- type: "string",
2027
- description: "ID of the memory to delete"
2028
- },
2029
- delete_relationships: {
2030
- type: "boolean",
2031
- description: "Also delete connected relationships. Default: false",
2032
- default: false
2052
+ var ConfirmationTokenService = class {
2053
+ EXPIRY_MINUTES = 5;
2054
+ /**
2055
+ * Create a new confirmation request
2056
+ *
2057
+ * @param userId - User ID who initiated the request
2058
+ * @param action - Action type (e.g., 'publish_memory')
2059
+ * @param payload - Data to store with the request
2060
+ * @param targetCollection - Optional target collection (e.g., 'the_void')
2061
+ * @returns Request ID and token
2062
+ */
2063
+ async createRequest(userId, action, payload, targetCollection) {
2064
+ try {
2065
+ const token = randomUUID();
2066
+ const now = /* @__PURE__ */ new Date();
2067
+ const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
2068
+ const request = {
2069
+ user_id: userId,
2070
+ token,
2071
+ action,
2072
+ target_collection: targetCollection,
2073
+ payload,
2074
+ created_at: now.toISOString(),
2075
+ expires_at: expiresAt.toISOString(),
2076
+ status: "pending"
2077
+ };
2078
+ const collectionPath = `users/${userId}/requests`;
2079
+ logger.info("Creating confirmation request", {
2080
+ service: "ConfirmationTokenService",
2081
+ userId,
2082
+ action,
2083
+ targetCollection,
2084
+ collectionPath,
2085
+ payloadKeys: Object.keys(payload)
2086
+ });
2087
+ logger.debug("Calling Firestore addDocument", {
2088
+ service: "ConfirmationTokenService",
2089
+ collectionPath
2090
+ });
2091
+ const docRef = await addDocument(collectionPath, request);
2092
+ logger.debug("Firestore addDocument returned", {
2093
+ service: "ConfirmationTokenService",
2094
+ hasDocRef: !!docRef,
2095
+ hasId: !!docRef?.id,
2096
+ docRefId: docRef?.id
2097
+ });
2098
+ if (!docRef) {
2099
+ const error = new Error("Firestore addDocument returned null/undefined");
2100
+ logger.error("CRITICAL: addDocument returned null", {
2101
+ service: "ConfirmationTokenService",
2102
+ userId,
2103
+ collectionPath
2104
+ });
2105
+ throw error;
2033
2106
  }
2034
- },
2035
- required: ["memory_id"]
2036
- }
2037
- };
2038
- async function handleDeleteMemory(args, userId) {
2039
- try {
2040
- logger.info("Deleting memory", { userId, memoryId: args.memory_id });
2041
- const collection = getMemoryCollection(userId);
2042
- const memory = await collection.query.fetchObjectById(args.memory_id, {
2043
- returnProperties: ["user_id", "doc_type", "relationships"]
2044
- });
2045
- if (!memory) {
2046
- throw new Error(`Memory not found: ${args.memory_id}`);
2047
- }
2048
- if (memory.properties.user_id !== userId) {
2049
- throw new Error("Unauthorized: Cannot delete another user's memory");
2050
- }
2051
- if (memory.properties.doc_type !== "memory") {
2052
- throw new Error("Cannot delete relationships using this tool. Use remember_delete_relationship instead.");
2053
- }
2054
- let relationshipsDeleted = 0;
2055
- if (args.delete_relationships && memory.properties.relationships) {
2056
- const relationshipIds = memory.properties.relationships;
2057
- for (const relId of relationshipIds) {
2058
- try {
2059
- await collection.data.deleteById(relId);
2060
- relationshipsDeleted++;
2061
- } catch (error) {
2062
- logger.warn(`Failed to delete relationship ${relId}:`, error);
2063
- }
2107
+ if (!docRef.id) {
2108
+ const error = new Error("Firestore addDocument returned docRef without ID");
2109
+ logger.error("CRITICAL: docRef has no ID", {
2110
+ service: "ConfirmationTokenService",
2111
+ userId,
2112
+ collectionPath,
2113
+ docRef
2114
+ });
2115
+ throw error;
2064
2116
  }
2117
+ logger.info("Confirmation request created successfully", {
2118
+ service: "ConfirmationTokenService",
2119
+ requestId: docRef.id,
2120
+ token,
2121
+ expiresAt: request.expires_at
2122
+ });
2123
+ return { requestId: docRef.id, token };
2124
+ } catch (error) {
2125
+ logger.error("Failed to create confirmation request", {
2126
+ service: "ConfirmationTokenService",
2127
+ error: error instanceof Error ? error.message : String(error),
2128
+ stack: error instanceof Error ? error.stack : void 0,
2129
+ userId,
2130
+ action,
2131
+ collectionPath: `users/${userId}/requests`
2132
+ });
2133
+ throw error;
2065
2134
  }
2066
- await collection.data.deleteById(args.memory_id);
2067
- logger.info("Memory deleted successfully", {
2135
+ }
2136
+ /**
2137
+ * Validate and retrieve a confirmation request
2138
+ *
2139
+ * @param userId - User ID
2140
+ * @param token - Confirmation token
2141
+ * @returns Request with request_id if valid, null otherwise
2142
+ */
2143
+ async validateToken(userId, token) {
2144
+ const collectionPath = `users/${userId}/requests`;
2145
+ logger.debug("Validating confirmation token", {
2146
+ service: "ConfirmationTokenService",
2068
2147
  userId,
2069
- memoryId: args.memory_id,
2070
- relationshipsDeleted
2148
+ token,
2149
+ collectionPath
2071
2150
  });
2072
- const result = {
2073
- memory_id: args.memory_id,
2074
- deleted: true,
2075
- relationships_deleted: relationshipsDeleted,
2076
- message: `Memory deleted successfully${relationshipsDeleted > 0 ? ` (${relationshipsDeleted} relationships also deleted)` : ""}`
2151
+ const queryOptions = {
2152
+ where: [
2153
+ { field: "token", op: "==", value: token },
2154
+ { field: "status", op: "==", value: "pending" }
2155
+ ],
2156
+ limit: 1
2077
2157
  };
2078
- return JSON.stringify(result, null, 2);
2079
- } catch (error) {
2080
- handleToolError(error, {
2081
- toolName: "remember_delete_memory",
2082
- operation: "delete memory",
2083
- userId,
2084
- memoryId: args.memory_id,
2085
- deleteRelationships: args.delete_relationships
2158
+ const results = await queryDocuments(collectionPath, queryOptions);
2159
+ logger.debug("Token query results", {
2160
+ service: "ConfirmationTokenService",
2161
+ resultsFound: results.length,
2162
+ hasResults: results.length > 0
2163
+ });
2164
+ if (results.length === 0) {
2165
+ logger.info("Token not found or not pending", {
2166
+ service: "ConfirmationTokenService",
2167
+ userId
2168
+ });
2169
+ return null;
2170
+ }
2171
+ const doc = results[0];
2172
+ const request = doc.data;
2173
+ logger.info("Confirmation request found", {
2174
+ service: "ConfirmationTokenService",
2175
+ requestId: doc.id,
2176
+ action: request.action,
2177
+ status: request.status,
2178
+ expiresAt: request.expires_at
2086
2179
  });
2180
+ const expiresAt = new Date(request.expires_at);
2181
+ if (expiresAt.getTime() < Date.now()) {
2182
+ logger.info("Token expired", {
2183
+ service: "ConfirmationTokenService",
2184
+ requestId: doc.id,
2185
+ expiresAt: request.expires_at
2186
+ });
2187
+ await this.updateStatus(userId, doc.id, "expired");
2188
+ return null;
2189
+ }
2190
+ return {
2191
+ ...request,
2192
+ request_id: doc.id
2193
+ };
2087
2194
  }
2088
- }
2089
-
2090
- // src/tools/update-memory.ts
2091
- init_logger();
2092
- var updateMemoryTool = {
2093
- name: "remember_update_memory",
2094
- description: `Update an existing memory with partial updates.
2095
-
2096
- Supports updating any field except id, user_id, doc_type, created_at.
2097
- Version number is automatically incremented and updated_at is set.
2098
- Only provided fields are updated (partial updates supported).
2099
-
2100
- Examples:
2101
- - "Update that camping note to add more details"
2102
- - "Change the weight of my recipe memory"
2103
- - "Add tags to the meeting note from yesterday"
2104
- `,
2105
- inputSchema: {
2106
- type: "object",
2107
- properties: {
2108
- memory_id: {
2109
- type: "string",
2110
- description: "ID of the memory to update"
2111
- },
2112
- content: {
2113
- type: "string",
2114
- description: "Updated memory content"
2115
- },
2116
- title: {
2117
- type: "string",
2118
- description: "Updated title"
2119
- },
2120
- type: {
2121
- type: "string",
2122
- description: "Updated content type"
2123
- },
2124
- weight: {
2125
- type: "number",
2126
- description: "Updated significance/priority (0-1)",
2127
- minimum: 0,
2128
- maximum: 1
2129
- },
2130
- trust: {
2131
- type: "number",
2132
- description: "Updated access control level (0-1)",
2133
- minimum: 0,
2134
- maximum: 1
2135
- },
2136
- tags: {
2137
- type: "array",
2138
- items: { type: "string" },
2139
- description: "Updated tags (replaces existing tags)"
2140
- },
2141
- references: {
2142
- type: "array",
2143
- items: { type: "string" },
2144
- description: "Updated source URLs (replaces existing references)"
2145
- },
2195
+ /**
2196
+ * Confirm a request
2197
+ *
2198
+ * @param userId - User ID
2199
+ * @param token - Confirmation token
2200
+ * @returns Confirmed request if valid, null otherwise
2201
+ */
2202
+ async confirmRequest(userId, token) {
2203
+ const request = await this.validateToken(userId, token);
2204
+ if (!request) {
2205
+ return null;
2206
+ }
2207
+ await this.updateStatus(userId, request.request_id, "confirmed");
2208
+ return {
2209
+ ...request,
2210
+ status: "confirmed",
2211
+ confirmed_at: (/* @__PURE__ */ new Date()).toISOString()
2212
+ };
2213
+ }
2214
+ /**
2215
+ * Deny a request
2216
+ *
2217
+ * @param userId - User ID
2218
+ * @param token - Confirmation token
2219
+ * @returns True if denied successfully, false otherwise
2220
+ */
2221
+ async denyRequest(userId, token) {
2222
+ const request = await this.validateToken(userId, token);
2223
+ if (!request) {
2224
+ return false;
2225
+ }
2226
+ await this.updateStatus(userId, request.request_id, "denied");
2227
+ return true;
2228
+ }
2229
+ /**
2230
+ * Retract a request
2231
+ *
2232
+ * @param userId - User ID
2233
+ * @param token - Confirmation token
2234
+ * @returns True if retracted successfully, false otherwise
2235
+ */
2236
+ async retractRequest(userId, token) {
2237
+ const request = await this.validateToken(userId, token);
2238
+ if (!request) {
2239
+ return false;
2240
+ }
2241
+ await this.updateStatus(userId, request.request_id, "retracted");
2242
+ return true;
2243
+ }
2244
+ /**
2245
+ * Update request status
2246
+ *
2247
+ * @param userId - User ID
2248
+ * @param requestId - Request document ID
2249
+ * @param status - New status
2250
+ */
2251
+ async updateStatus(userId, requestId, status) {
2252
+ const collectionPath = `users/${userId}/requests`;
2253
+ const updateData = {
2254
+ status
2255
+ };
2256
+ if (status === "confirmed") {
2257
+ updateData.confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
2258
+ }
2259
+ await updateDocument(collectionPath, requestId, updateData);
2260
+ }
2261
+ /**
2262
+ * Clean up expired requests (optional - Firestore TTL handles deletion)
2263
+ *
2264
+ * Note: Configure Firestore TTL policy on 'requests' collection group
2265
+ * with 'expires_at' field for automatic deletion within 24 hours.
2266
+ *
2267
+ * This method is optional for immediate cleanup if needed.
2268
+ *
2269
+ * @returns Count of deleted requests
2270
+ */
2271
+ async cleanupExpired() {
2272
+ logger.warn("cleanupExpired not implemented - relying on Firestore TTL", {
2273
+ service: "ConfirmationTokenService",
2274
+ note: "Configure Firestore TTL policy on requests collection group"
2275
+ });
2276
+ return 0;
2277
+ }
2278
+ };
2279
+ var confirmationTokenService = new ConfirmationTokenService();
2280
+
2281
+ // src/tools/delete-memory.ts
2282
+ init_logger();
2283
+ var deleteMemoryTool = {
2284
+ name: "remember_delete_memory",
2285
+ description: `Request to delete a memory. Requires confirmation via remember_confirm.
2286
+
2287
+ \u26A0\uFE0F **IMPORTANT**: This is a two-step process:
2288
+ 1. Call remember_delete_memory to request deletion (returns token)
2289
+ 2. User must confirm via remember_confirm with the token
2290
+
2291
+ The memory will be soft-deleted (marked as deleted but not removed from database).
2292
+
2293
+ Examples:
2294
+ - "Delete that old camping note"
2295
+ - "Remove the recipe I saved yesterday"
2296
+ `,
2297
+ inputSchema: {
2298
+ type: "object",
2299
+ properties: {
2300
+ memory_id: {
2301
+ type: "string",
2302
+ description: "ID of the memory to delete"
2303
+ },
2304
+ reason: {
2305
+ type: "string",
2306
+ description: "Optional reason for deletion"
2307
+ }
2308
+ },
2309
+ required: ["memory_id"]
2310
+ }
2311
+ };
2312
+ async function handleDeleteMemory(args, userId) {
2313
+ try {
2314
+ logger.info("Requesting memory deletion", {
2315
+ userId,
2316
+ memoryId: args.memory_id,
2317
+ hasReason: !!args.reason
2318
+ });
2319
+ const { memory_id, reason } = args;
2320
+ const client2 = getWeaviateClient();
2321
+ const collectionName = `Memory_${sanitizeUserId(userId)}`;
2322
+ const collection = client2.collections.get(collectionName);
2323
+ const memory = await fetchMemoryWithAllProperties(collection, memory_id);
2324
+ if (!memory) {
2325
+ throw new Error(`Memory not found: ${memory_id}`);
2326
+ }
2327
+ if (memory.properties.user_id !== userId) {
2328
+ throw new Error(`Cannot delete memory: not owned by user ${userId}`);
2329
+ }
2330
+ if (memory.properties.doc_type !== "memory") {
2331
+ throw new Error("Cannot delete relationships using this tool. Use remember_delete_relationship instead.");
2332
+ }
2333
+ if (memory.properties.deleted_at) {
2334
+ throw new Error(`Memory ${memory_id} is already deleted`);
2335
+ }
2336
+ const relationshipsResult = await collection.query.fetchObjects({
2337
+ filters: Filters2.and(
2338
+ collection.filter.byProperty("doc_type").equal("relationship"),
2339
+ collection.filter.byProperty("memory_ids").containsAny([memory_id])
2340
+ ),
2341
+ limit: 100
2342
+ });
2343
+ const orphanedRelationships = relationshipsResult.objects.map((r) => r.uuid);
2344
+ logger.info("Found relationships to orphan", {
2345
+ userId,
2346
+ memoryId: memory_id,
2347
+ relationshipCount: orphanedRelationships.length
2348
+ });
2349
+ const { requestId, token } = await confirmationTokenService.createRequest(
2350
+ userId,
2351
+ "delete_memory",
2352
+ {
2353
+ memory_id,
2354
+ reason: reason || null
2355
+ }
2356
+ );
2357
+ const expiresAt = new Date(Date.now() + 5 * 60 * 1e3);
2358
+ logger.info("Delete confirmation token created", {
2359
+ userId,
2360
+ memoryId: memory_id,
2361
+ requestId,
2362
+ token,
2363
+ expiresAt: expiresAt.toISOString()
2364
+ });
2365
+ return JSON.stringify(
2366
+ {
2367
+ success: true,
2368
+ token,
2369
+ expires_at: expiresAt.toISOString(),
2370
+ preview: {
2371
+ memory_id,
2372
+ content: memory.properties.content?.substring(0, 200) + (memory.properties.content?.length > 200 ? "..." : ""),
2373
+ type: memory.properties.type,
2374
+ relationships_count: orphanedRelationships.length,
2375
+ will_orphan: orphanedRelationships
2376
+ },
2377
+ message: `Deletion requested. Use remember_confirm with token to complete deletion. Token expires in 5 minutes.`
2378
+ },
2379
+ null,
2380
+ 2
2381
+ );
2382
+ } catch (error) {
2383
+ handleToolError(error, {
2384
+ toolName: "remember_delete_memory",
2385
+ userId,
2386
+ operation: "request delete",
2387
+ memoryId: args.memory_id
2388
+ });
2389
+ }
2390
+ }
2391
+
2392
+ // src/tools/update-memory.ts
2393
+ init_logger();
2394
+ var updateMemoryTool = {
2395
+ name: "remember_update_memory",
2396
+ description: `Update an existing memory with partial updates.
2397
+
2398
+ Supports updating any field except id, user_id, doc_type, created_at.
2399
+ Version number is automatically incremented and updated_at is set.
2400
+ Only provided fields are updated (partial updates supported).
2401
+
2402
+ Examples:
2403
+ - "Update that camping note to add more details"
2404
+ - "Change the weight of my recipe memory"
2405
+ - "Add tags to the meeting note from yesterday"
2406
+ `,
2407
+ inputSchema: {
2408
+ type: "object",
2409
+ properties: {
2410
+ memory_id: {
2411
+ type: "string",
2412
+ description: "ID of the memory to update"
2413
+ },
2414
+ content: {
2415
+ type: "string",
2416
+ description: "Updated memory content"
2417
+ },
2418
+ title: {
2419
+ type: "string",
2420
+ description: "Updated title"
2421
+ },
2422
+ type: {
2423
+ type: "string",
2424
+ description: "Updated content type"
2425
+ },
2426
+ weight: {
2427
+ type: "number",
2428
+ description: "Updated significance/priority (0-1)",
2429
+ minimum: 0,
2430
+ maximum: 1
2431
+ },
2432
+ trust: {
2433
+ type: "number",
2434
+ description: "Updated access control level (0-1)",
2435
+ minimum: 0,
2436
+ maximum: 1
2437
+ },
2438
+ tags: {
2439
+ type: "array",
2440
+ items: { type: "string" },
2441
+ description: "Updated tags (replaces existing tags)"
2442
+ },
2443
+ references: {
2444
+ type: "array",
2445
+ items: { type: "string" },
2446
+ description: "Updated source URLs (replaces existing references)"
2447
+ },
2146
2448
  structured_content: {
2147
2449
  type: "object",
2148
2450
  description: "Updated structured content"
@@ -2190,6 +2492,10 @@ async function handleUpdateMemory(args, userId) {
2190
2492
  if (existingMemory.properties.doc_type !== "memory") {
2191
2493
  throw new Error("Cannot update relationships using this tool. Use remember_update_relationship instead.");
2192
2494
  }
2495
+ if (existingMemory.properties.deleted_at) {
2496
+ const deletedAt = typeof existingMemory.properties.deleted_at === "string" ? existingMemory.properties.deleted_at : new Date(existingMemory.properties.deleted_at).toISOString();
2497
+ throw new Error(`Cannot update deleted memory: ${args.memory_id}. Memory was deleted on ${deletedAt}.`);
2498
+ }
2193
2499
  const updates = {};
2194
2500
  const updatedFields = [];
2195
2501
  if (args.content !== void 0) {
@@ -2357,6 +2663,12 @@ var findSimilarTool = {
2357
2663
  type: "boolean",
2358
2664
  description: "Include relationships in results. Default: false",
2359
2665
  default: false
2666
+ },
2667
+ deleted_filter: {
2668
+ type: "string",
2669
+ enum: ["exclude", "include", "only"],
2670
+ default: "exclude",
2671
+ description: 'Filter deleted memories: "exclude" (default, hide deleted), "include" (show all), "only" (show only deleted)'
2360
2672
  }
2361
2673
  }
2362
2674
  }
@@ -2373,6 +2685,7 @@ async function handleFindSimilar(args, userId) {
2373
2685
  const collection = getMemoryCollection(userId);
2374
2686
  const limit = args.limit ?? 10;
2375
2687
  const minSimilarity = args.min_similarity ?? 0.7;
2688
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
2376
2689
  let results;
2377
2690
  if (args.memory_id) {
2378
2691
  const memory = await collection.query.fetchObjectById(args.memory_id, {
@@ -2387,20 +2700,28 @@ async function handleFindSimilar(args, userId) {
2387
2700
  if (memory.properties.doc_type !== "memory") {
2388
2701
  throw new Error("Can only find similar memories for memory documents, not relationships");
2389
2702
  }
2390
- results = await collection.query.nearObject(args.memory_id, {
2703
+ const searchOptions = {
2391
2704
  limit: limit + 1,
2392
2705
  // +1 to exclude the source memory itself
2393
2706
  distance: 1 - minSimilarity,
2394
2707
  // Convert similarity to distance
2395
2708
  returnMetadata: ["distance"]
2396
- });
2709
+ };
2710
+ if (deletedFilter) {
2711
+ searchOptions.filters = deletedFilter;
2712
+ }
2713
+ results = await collection.query.nearObject(args.memory_id, searchOptions);
2397
2714
  results.objects = results.objects.filter((obj) => obj.uuid !== args.memory_id);
2398
2715
  } else {
2399
- results = await collection.query.nearText(args.text, {
2716
+ const searchOptions = {
2400
2717
  limit,
2401
2718
  distance: 1 - minSimilarity,
2402
2719
  returnMetadata: ["distance"]
2403
- });
2720
+ };
2721
+ if (deletedFilter) {
2722
+ searchOptions.filters = deletedFilter;
2723
+ }
2724
+ results = await collection.query.nearText(args.text, searchOptions);
2404
2725
  }
2405
2726
  if (!args.include_relationships) {
2406
2727
  results.objects = results.objects.filter(
@@ -2547,6 +2868,12 @@ var queryMemoryTool = {
2547
2868
  description: 'Output format: "detailed" (full objects) or "compact" (text summary). Default: detailed',
2548
2869
  enum: ["detailed", "compact"],
2549
2870
  default: "detailed"
2871
+ },
2872
+ deleted_filter: {
2873
+ type: "string",
2874
+ enum: ["exclude", "include", "only"],
2875
+ default: "exclude",
2876
+ description: 'Filter deleted memories: "exclude" (default, hide deleted), "include" (show all), "only" (show only deleted)'
2550
2877
  }
2551
2878
  },
2552
2879
  required: ["query"]
@@ -2563,15 +2890,17 @@ async function handleQueryMemory(args, userId) {
2563
2890
  const minRelevance = args.min_relevance ?? 0.6;
2564
2891
  const includeContext = args.include_context ?? true;
2565
2892
  const format = args.format ?? "detailed";
2566
- const filters = buildCombinedSearchFilters(collection, args.filters);
2893
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
2894
+ const searchFilters = buildCombinedSearchFilters(collection, args.filters);
2895
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, searchFilters].filter((f) => f !== null));
2567
2896
  const searchOptions = {
2568
2897
  limit,
2569
2898
  distance: 1 - minRelevance,
2570
2899
  // Convert relevance to distance
2571
2900
  returnMetadata: ["distance"]
2572
2901
  };
2573
- if (filters) {
2574
- searchOptions.filters = filters;
2902
+ if (combinedFilters) {
2903
+ searchOptions.filters = combinedFilters;
2575
2904
  }
2576
2905
  const results = await collection.query.nearText(args.query, searchOptions);
2577
2906
  const relevantMemories = results.objects.map((obj) => {
@@ -2702,7 +3031,7 @@ async function handleCreateRelationship(args, userId, context) {
2702
3031
  args.memory_ids.map(async (memoryId) => {
2703
3032
  try {
2704
3033
  const memory = await collection.query.fetchObjectById(memoryId, {
2705
- returnProperties: ["user_id", "doc_type", "relationships"]
3034
+ returnProperties: ["user_id", "doc_type", "relationships", "deleted_at"]
2706
3035
  });
2707
3036
  if (!memory) {
2708
3037
  logger.warn("Memory not found", { userId, memoryId });
@@ -2724,6 +3053,15 @@ async function handleCreateRelationship(args, userId, context) {
2724
3053
  });
2725
3054
  return { memoryId, error: "Cannot create relationship with non-memory document" };
2726
3055
  }
3056
+ if (memory.properties.deleted_at) {
3057
+ const deletedAt = typeof memory.properties.deleted_at === "string" ? memory.properties.deleted_at : new Date(memory.properties.deleted_at).toISOString();
3058
+ logger.warn("Attempt to create relationship with deleted memory", {
3059
+ userId,
3060
+ memoryId,
3061
+ deletedAt
3062
+ });
3063
+ return { memoryId, error: `Memory is deleted (deleted on ${deletedAt})` };
3064
+ }
2727
3065
  return {
2728
3066
  memoryId,
2729
3067
  memory,
@@ -2970,7 +3308,7 @@ async function handleUpdateRelationship(args, userId) {
2970
3308
  }
2971
3309
 
2972
3310
  // src/tools/search-relationship.ts
2973
- import { Filters as Filters2 } from "weaviate-client";
3311
+ import { Filters as Filters3 } from "weaviate-client";
2974
3312
  init_logger();
2975
3313
  var searchRelationshipTool = {
2976
3314
  name: "remember_search_relationship",
@@ -3024,6 +3362,12 @@ var searchRelationshipTool = {
3024
3362
  type: "number",
3025
3363
  description: "Offset for pagination (default: 0)",
3026
3364
  minimum: 0
3365
+ },
3366
+ deleted_filter: {
3367
+ type: "string",
3368
+ enum: ["exclude", "include", "only"],
3369
+ default: "exclude",
3370
+ description: 'Filter deleted memories: "exclude" (default, hide deleted), "include" (show all), "only" (show only deleted)'
3027
3371
  }
3028
3372
  },
3029
3373
  required: ["query"]
@@ -3039,7 +3383,11 @@ async function handleSearchRelationship(args, userId) {
3039
3383
  const collection = getMemoryCollection(userId);
3040
3384
  const limit = args.limit ?? 10;
3041
3385
  const offset = args.offset ?? 0;
3386
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
3042
3387
  const filterList = [];
3388
+ if (deletedFilter) {
3389
+ filterList.push(deletedFilter);
3390
+ }
3043
3391
  filterList.push(
3044
3392
  collection.filter.byProperty("doc_type").equal("relationship")
3045
3393
  );
@@ -3052,7 +3400,7 @@ async function handleSearchRelationship(args, userId) {
3052
3400
  const typeFilters = args.relationship_types.map(
3053
3401
  (type) => collection.filter.byProperty("relationship_type").equal(type)
3054
3402
  );
3055
- filterList.push(Filters2.or(...typeFilters));
3403
+ filterList.push(Filters3.or(...typeFilters));
3056
3404
  }
3057
3405
  }
3058
3406
  if (args.strength_min !== void 0) {
@@ -3070,7 +3418,7 @@ async function handleSearchRelationship(args, userId) {
3070
3418
  collection.filter.byProperty("tags").containsAny(args.tags)
3071
3419
  );
3072
3420
  }
3073
- const combinedFilters = filterList.length > 1 ? Filters2.and(...filterList) : filterList[0];
3421
+ const combinedFilters = combineFiltersWithAnd(filterList);
3074
3422
  const searchOptions = {
3075
3423
  alpha: 1,
3076
3424
  // Pure semantic search for relationships
@@ -3540,369 +3888,137 @@ Common examples:
3540
3888
  type: "object",
3541
3889
  properties: {
3542
3890
  preferences: {
3543
- ...getPreferencesSchema(),
3544
- description: "Partial preferences object with fields to update"
3545
- }
3546
- },
3547
- required: ["preferences"]
3548
- }
3549
- };
3550
- function formatPreferenceChangeMessage(updates) {
3551
- const changes = [];
3552
- if (updates.templates) {
3553
- if (updates.templates.auto_suggest !== void 0) {
3554
- changes.push(
3555
- updates.templates.auto_suggest ? "Template suggestions enabled" : "Template suggestions disabled"
3556
- );
3557
- }
3558
- if (updates.templates.suppressed_categories) {
3559
- changes.push(`Suppressed categories: ${updates.templates.suppressed_categories.join(", ")}`);
3560
- }
3561
- }
3562
- if (updates.search) {
3563
- if (updates.search.default_limit !== void 0) {
3564
- changes.push(`Search limit set to ${updates.search.default_limit}`);
3565
- }
3566
- if (updates.search.default_alpha !== void 0) {
3567
- changes.push(`Search alpha set to ${updates.search.default_alpha}`);
3568
- }
3569
- }
3570
- if (updates.privacy) {
3571
- if (updates.privacy.default_trust_level !== void 0) {
3572
- changes.push(`Default trust level set to ${updates.privacy.default_trust_level}`);
3573
- }
3574
- }
3575
- if (updates.location) {
3576
- if (updates.location.auto_capture !== void 0) {
3577
- changes.push(
3578
- updates.location.auto_capture ? "Location auto-capture enabled" : "Location auto-capture disabled"
3579
- );
3580
- }
3581
- }
3582
- if (changes.length === 0) {
3583
- return "Preferences updated successfully";
3584
- }
3585
- return `Preferences updated: ${changes.join(", ")}`;
3586
- }
3587
- async function handleSetPreference(args, userId) {
3588
- try {
3589
- const { preferences } = args;
3590
- logger.info("Setting preferences", { userId, updates: Object.keys(preferences) });
3591
- const updatedPreferences = await PreferencesDatabaseService.updatePreferences(
3592
- userId,
3593
- preferences
3594
- );
3595
- const message = formatPreferenceChangeMessage(preferences);
3596
- const result = {
3597
- success: true,
3598
- updated_preferences: updatedPreferences,
3599
- message
3600
- };
3601
- logger.info("Preferences set successfully", { userId });
3602
- return JSON.stringify(result, null, 2);
3603
- } catch (error) {
3604
- handleToolError(error, {
3605
- toolName: "remember_set_preference",
3606
- operation: "set preference",
3607
- userId,
3608
- preferencesProvided: Object.keys(args.preferences || {}).length
3609
- });
3610
- }
3611
- }
3612
-
3613
- // src/tools/get-preferences.ts
3614
- init_logger();
3615
- var getPreferencesTool = {
3616
- name: "remember_get_preferences",
3617
- description: `Get current user preferences.
3618
-
3619
- Use this to understand user's current settings before suggesting changes
3620
- or to explain why system is behaving a certain way.
3621
-
3622
- Returns the complete preferences object or filtered by category.
3623
- If preferences don't exist, returns defaults.
3624
-
3625
- ${getPreferenceDescription()}
3626
- `,
3627
- inputSchema: {
3628
- type: "object",
3629
- properties: {
3630
- category: {
3631
- type: "string",
3632
- enum: ["templates", "search", "location", "privacy", "notifications", "display"],
3633
- description: "Optional category to filter preferences"
3634
- }
3635
- }
3636
- }
3637
- };
3638
- async function handleGetPreferences(args, userId) {
3639
- try {
3640
- const { category } = args;
3641
- logger.info("Getting preferences", { userId, category });
3642
- const preferences = await PreferencesDatabaseService.getPreferences(userId);
3643
- const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
3644
- let result;
3645
- let message;
3646
- if (category) {
3647
- if (!PREFERENCE_CATEGORIES.includes(category)) {
3648
- throw new Error(`Invalid category: ${category}. Valid categories: ${PREFERENCE_CATEGORIES.join(", ")}`);
3649
- }
3650
- result = {
3651
- [category]: preferences[category]
3652
- };
3653
- message = isDefault ? `Showing default ${category} preferences (user has not customized preferences yet).` : `Showing current ${category} preferences.`;
3654
- } else {
3655
- result = preferences;
3656
- message = isDefault ? "Showing default preferences (user has not customized preferences yet)." : "Showing current user preferences.";
3657
- }
3658
- const response = {
3659
- preferences: result,
3660
- is_default: isDefault,
3661
- message
3662
- };
3663
- logger.info("Preferences retrieved successfully", { userId, category, isDefault });
3664
- return JSON.stringify(response, null, 2);
3665
- } catch (error) {
3666
- handleToolError(error, {
3667
- toolName: "remember_get_preferences",
3668
- operation: "get preferences",
3669
- userId,
3670
- category: args.category
3671
- });
3672
- }
3673
- }
3674
-
3675
- // src/services/confirmation-token.service.ts
3676
- import { randomUUID } from "crypto";
3677
- init_logger();
3678
- var ConfirmationTokenService = class {
3679
- EXPIRY_MINUTES = 5;
3680
- /**
3681
- * Create a new confirmation request
3682
- *
3683
- * @param userId - User ID who initiated the request
3684
- * @param action - Action type (e.g., 'publish_memory')
3685
- * @param payload - Data to store with the request
3686
- * @param targetCollection - Optional target collection (e.g., 'the_void')
3687
- * @returns Request ID and token
3688
- */
3689
- async createRequest(userId, action, payload, targetCollection) {
3690
- try {
3691
- const token = randomUUID();
3692
- const now = /* @__PURE__ */ new Date();
3693
- const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
3694
- const request = {
3695
- user_id: userId,
3696
- token,
3697
- action,
3698
- target_collection: targetCollection,
3699
- payload,
3700
- created_at: now.toISOString(),
3701
- expires_at: expiresAt.toISOString(),
3702
- status: "pending"
3703
- };
3704
- const collectionPath = `users/${userId}/requests`;
3705
- logger.info("Creating confirmation request", {
3706
- service: "ConfirmationTokenService",
3707
- userId,
3708
- action,
3709
- targetCollection,
3710
- collectionPath,
3711
- payloadKeys: Object.keys(payload)
3712
- });
3713
- logger.debug("Calling Firestore addDocument", {
3714
- service: "ConfirmationTokenService",
3715
- collectionPath
3716
- });
3717
- const docRef = await addDocument(collectionPath, request);
3718
- logger.debug("Firestore addDocument returned", {
3719
- service: "ConfirmationTokenService",
3720
- hasDocRef: !!docRef,
3721
- hasId: !!docRef?.id,
3722
- docRefId: docRef?.id
3723
- });
3724
- if (!docRef) {
3725
- const error = new Error("Firestore addDocument returned null/undefined");
3726
- logger.error("CRITICAL: addDocument returned null", {
3727
- service: "ConfirmationTokenService",
3728
- userId,
3729
- collectionPath
3730
- });
3731
- throw error;
3732
- }
3733
- if (!docRef.id) {
3734
- const error = new Error("Firestore addDocument returned docRef without ID");
3735
- logger.error("CRITICAL: docRef has no ID", {
3736
- service: "ConfirmationTokenService",
3737
- userId,
3738
- collectionPath,
3739
- docRef
3740
- });
3741
- throw error;
3742
- }
3743
- logger.info("Confirmation request created successfully", {
3744
- service: "ConfirmationTokenService",
3745
- requestId: docRef.id,
3746
- token,
3747
- expiresAt: request.expires_at
3748
- });
3749
- return { requestId: docRef.id, token };
3750
- } catch (error) {
3751
- logger.error("Failed to create confirmation request", {
3752
- service: "ConfirmationTokenService",
3753
- error: error instanceof Error ? error.message : String(error),
3754
- stack: error instanceof Error ? error.stack : void 0,
3755
- userId,
3756
- action,
3757
- collectionPath: `users/${userId}/requests`
3758
- });
3759
- throw error;
3760
- }
3761
- }
3762
- /**
3763
- * Validate and retrieve a confirmation request
3764
- *
3765
- * @param userId - User ID
3766
- * @param token - Confirmation token
3767
- * @returns Request with request_id if valid, null otherwise
3768
- */
3769
- async validateToken(userId, token) {
3770
- const collectionPath = `users/${userId}/requests`;
3771
- logger.debug("Validating confirmation token", {
3772
- service: "ConfirmationTokenService",
3773
- userId,
3774
- token,
3775
- collectionPath
3776
- });
3777
- const queryOptions = {
3778
- where: [
3779
- { field: "token", op: "==", value: token },
3780
- { field: "status", op: "==", value: "pending" }
3781
- ],
3782
- limit: 1
3783
- };
3784
- const results = await queryDocuments(collectionPath, queryOptions);
3785
- logger.debug("Token query results", {
3786
- service: "ConfirmationTokenService",
3787
- resultsFound: results.length,
3788
- hasResults: results.length > 0
3789
- });
3790
- if (results.length === 0) {
3791
- logger.info("Token not found or not pending", {
3792
- service: "ConfirmationTokenService",
3793
- userId
3794
- });
3795
- return null;
3891
+ ...getPreferencesSchema(),
3892
+ description: "Partial preferences object with fields to update"
3893
+ }
3894
+ },
3895
+ required: ["preferences"]
3896
+ }
3897
+ };
3898
+ function formatPreferenceChangeMessage(updates) {
3899
+ const changes = [];
3900
+ if (updates.templates) {
3901
+ if (updates.templates.auto_suggest !== void 0) {
3902
+ changes.push(
3903
+ updates.templates.auto_suggest ? "Template suggestions enabled" : "Template suggestions disabled"
3904
+ );
3796
3905
  }
3797
- const doc = results[0];
3798
- const request = doc.data;
3799
- logger.info("Confirmation request found", {
3800
- service: "ConfirmationTokenService",
3801
- requestId: doc.id,
3802
- action: request.action,
3803
- status: request.status,
3804
- expiresAt: request.expires_at
3805
- });
3806
- const expiresAt = new Date(request.expires_at);
3807
- if (expiresAt.getTime() < Date.now()) {
3808
- logger.info("Token expired", {
3809
- service: "ConfirmationTokenService",
3810
- requestId: doc.id,
3811
- expiresAt: request.expires_at
3812
- });
3813
- await this.updateStatus(userId, doc.id, "expired");
3814
- return null;
3906
+ if (updates.templates.suppressed_categories) {
3907
+ changes.push(`Suppressed categories: ${updates.templates.suppressed_categories.join(", ")}`);
3815
3908
  }
3816
- return {
3817
- ...request,
3818
- request_id: doc.id
3819
- };
3820
3909
  }
3821
- /**
3822
- * Confirm a request
3823
- *
3824
- * @param userId - User ID
3825
- * @param token - Confirmation token
3826
- * @returns Confirmed request if valid, null otherwise
3827
- */
3828
- async confirmRequest(userId, token) {
3829
- const request = await this.validateToken(userId, token);
3830
- if (!request) {
3831
- return null;
3910
+ if (updates.search) {
3911
+ if (updates.search.default_limit !== void 0) {
3912
+ changes.push(`Search limit set to ${updates.search.default_limit}`);
3913
+ }
3914
+ if (updates.search.default_alpha !== void 0) {
3915
+ changes.push(`Search alpha set to ${updates.search.default_alpha}`);
3832
3916
  }
3833
- await this.updateStatus(userId, request.request_id, "confirmed");
3834
- return {
3835
- ...request,
3836
- status: "confirmed",
3837
- confirmed_at: (/* @__PURE__ */ new Date()).toISOString()
3838
- };
3839
3917
  }
3840
- /**
3841
- * Deny a request
3842
- *
3843
- * @param userId - User ID
3844
- * @param token - Confirmation token
3845
- * @returns True if denied successfully, false otherwise
3846
- */
3847
- async denyRequest(userId, token) {
3848
- const request = await this.validateToken(userId, token);
3849
- if (!request) {
3850
- return false;
3918
+ if (updates.privacy) {
3919
+ if (updates.privacy.default_trust_level !== void 0) {
3920
+ changes.push(`Default trust level set to ${updates.privacy.default_trust_level}`);
3851
3921
  }
3852
- await this.updateStatus(userId, request.request_id, "denied");
3853
- return true;
3854
3922
  }
3855
- /**
3856
- * Retract a request
3857
- *
3858
- * @param userId - User ID
3859
- * @param token - Confirmation token
3860
- * @returns True if retracted successfully, false otherwise
3861
- */
3862
- async retractRequest(userId, token) {
3863
- const request = await this.validateToken(userId, token);
3864
- if (!request) {
3865
- return false;
3923
+ if (updates.location) {
3924
+ if (updates.location.auto_capture !== void 0) {
3925
+ changes.push(
3926
+ updates.location.auto_capture ? "Location auto-capture enabled" : "Location auto-capture disabled"
3927
+ );
3866
3928
  }
3867
- await this.updateStatus(userId, request.request_id, "retracted");
3868
- return true;
3869
3929
  }
3870
- /**
3871
- * Update request status
3872
- *
3873
- * @param userId - User ID
3874
- * @param requestId - Request document ID
3875
- * @param status - New status
3876
- */
3877
- async updateStatus(userId, requestId, status) {
3878
- const collectionPath = `users/${userId}/requests`;
3879
- const updateData = {
3880
- status
3930
+ if (changes.length === 0) {
3931
+ return "Preferences updated successfully";
3932
+ }
3933
+ return `Preferences updated: ${changes.join(", ")}`;
3934
+ }
3935
+ async function handleSetPreference(args, userId) {
3936
+ try {
3937
+ const { preferences } = args;
3938
+ logger.info("Setting preferences", { userId, updates: Object.keys(preferences) });
3939
+ const updatedPreferences = await PreferencesDatabaseService.updatePreferences(
3940
+ userId,
3941
+ preferences
3942
+ );
3943
+ const message = formatPreferenceChangeMessage(preferences);
3944
+ const result = {
3945
+ success: true,
3946
+ updated_preferences: updatedPreferences,
3947
+ message
3881
3948
  };
3882
- if (status === "confirmed") {
3883
- updateData.confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
3949
+ logger.info("Preferences set successfully", { userId });
3950
+ return JSON.stringify(result, null, 2);
3951
+ } catch (error) {
3952
+ handleToolError(error, {
3953
+ toolName: "remember_set_preference",
3954
+ operation: "set preference",
3955
+ userId,
3956
+ preferencesProvided: Object.keys(args.preferences || {}).length
3957
+ });
3958
+ }
3959
+ }
3960
+
3961
+ // src/tools/get-preferences.ts
3962
+ init_logger();
3963
+ var getPreferencesTool = {
3964
+ name: "remember_get_preferences",
3965
+ description: `Get current user preferences.
3966
+
3967
+ Use this to understand user's current settings before suggesting changes
3968
+ or to explain why system is behaving a certain way.
3969
+
3970
+ Returns the complete preferences object or filtered by category.
3971
+ If preferences don't exist, returns defaults.
3972
+
3973
+ ${getPreferenceDescription()}
3974
+ `,
3975
+ inputSchema: {
3976
+ type: "object",
3977
+ properties: {
3978
+ category: {
3979
+ type: "string",
3980
+ enum: ["templates", "search", "location", "privacy", "notifications", "display"],
3981
+ description: "Optional category to filter preferences"
3982
+ }
3884
3983
  }
3885
- await updateDocument(collectionPath, requestId, updateData);
3886
3984
  }
3887
- /**
3888
- * Clean up expired requests (optional - Firestore TTL handles deletion)
3889
- *
3890
- * Note: Configure Firestore TTL policy on 'requests' collection group
3891
- * with 'expires_at' field for automatic deletion within 24 hours.
3892
- *
3893
- * This method is optional for immediate cleanup if needed.
3894
- *
3895
- * @returns Count of deleted requests
3896
- */
3897
- async cleanupExpired() {
3898
- logger.warn("cleanupExpired not implemented - relying on Firestore TTL", {
3899
- service: "ConfirmationTokenService",
3900
- note: "Configure Firestore TTL policy on requests collection group"
3985
+ };
3986
+ async function handleGetPreferences(args, userId) {
3987
+ try {
3988
+ const { category } = args;
3989
+ logger.info("Getting preferences", { userId, category });
3990
+ const preferences = await PreferencesDatabaseService.getPreferences(userId);
3991
+ const isDefault = !preferences.created_at || preferences.created_at === preferences.updated_at;
3992
+ let result;
3993
+ let message;
3994
+ if (category) {
3995
+ if (!PREFERENCE_CATEGORIES.includes(category)) {
3996
+ throw new Error(`Invalid category: ${category}. Valid categories: ${PREFERENCE_CATEGORIES.join(", ")}`);
3997
+ }
3998
+ result = {
3999
+ [category]: preferences[category]
4000
+ };
4001
+ message = isDefault ? `Showing default ${category} preferences (user has not customized preferences yet).` : `Showing current ${category} preferences.`;
4002
+ } else {
4003
+ result = preferences;
4004
+ message = isDefault ? "Showing default preferences (user has not customized preferences yet)." : "Showing current user preferences.";
4005
+ }
4006
+ const response = {
4007
+ preferences: result,
4008
+ is_default: isDefault,
4009
+ message
4010
+ };
4011
+ logger.info("Preferences retrieved successfully", { userId, category, isDefault });
4012
+ return JSON.stringify(response, null, 2);
4013
+ } catch (error) {
4014
+ handleToolError(error, {
4015
+ toolName: "remember_get_preferences",
4016
+ operation: "get preferences",
4017
+ userId,
4018
+ category: args.category
3901
4019
  });
3902
- return 0;
3903
4020
  }
3904
- };
3905
- var confirmationTokenService = new ConfirmationTokenService();
4021
+ }
3906
4022
 
3907
4023
  // src/weaviate/space-schema.ts
3908
4024
  init_space_memory();
@@ -3944,11 +4060,6 @@ async function createSpaceCollection(client2, spaceId) {
3944
4060
  dataType: "text[]",
3945
4061
  description: 'Spaces this memory is published to (e.g., ["the_void", "dogs"])'
3946
4062
  },
3947
- {
3948
- name: "space_id",
3949
- dataType: "text",
3950
- description: "DEPRECATED: Use spaces array instead. Will be removed in v3.0.0."
3951
- },
3952
4063
  {
3953
4064
  name: "author_id",
3954
4065
  dataType: "text",
@@ -4173,6 +4284,22 @@ async function createSpaceCollection(client2, spaceId) {
4173
4284
  name: "moderation_flags",
4174
4285
  dataType: "text[]",
4175
4286
  description: 'Per-space moderation flags (format: "{space_id}:{flag_type}")'
4287
+ },
4288
+ // Soft delete fields
4289
+ {
4290
+ name: "deleted_at",
4291
+ dataType: "date",
4292
+ description: "Timestamp when memory was soft-deleted (null = not deleted)"
4293
+ },
4294
+ {
4295
+ name: "deleted_by",
4296
+ dataType: "text",
4297
+ description: "User ID who deleted the memory"
4298
+ },
4299
+ {
4300
+ name: "deletion_reason",
4301
+ dataType: "text",
4302
+ description: "Optional reason for deletion"
4176
4303
  }
4177
4304
  ]
4178
4305
  });
@@ -4472,6 +4599,9 @@ async function handleConfirm(args, userId) {
4472
4599
  if (request.action === "publish_memory") {
4473
4600
  return await executePublishMemory(request, userId);
4474
4601
  }
4602
+ if (request.action === "delete_memory") {
4603
+ return await executeDeleteMemory(request, userId);
4604
+ }
4475
4605
  throw new Error(`Unknown action type: ${request.action}`);
4476
4606
  } catch (error) {
4477
4607
  debug.error("Tool failed", {
@@ -4684,6 +4814,52 @@ async function executePublishMemory(request, userId) {
4684
4814
  });
4685
4815
  }
4686
4816
  }
4817
+ async function executeDeleteMemory(request, userId) {
4818
+ try {
4819
+ logger.info("Executing delete memory action", {
4820
+ function: "executeDeleteMemory",
4821
+ userId,
4822
+ memoryId: request.payload.memory_id,
4823
+ hasReason: !!request.payload.reason
4824
+ });
4825
+ const { memory_id, reason } = request.payload;
4826
+ const client2 = getWeaviateClient();
4827
+ const collectionName = `Memory_${sanitizeUserId(userId)}`;
4828
+ const collection = client2.collections.get(collectionName);
4829
+ await collection.data.update({
4830
+ id: memory_id,
4831
+ properties: {
4832
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
4833
+ deleted_by: userId,
4834
+ deletion_reason: reason || null
4835
+ }
4836
+ });
4837
+ logger.info("Memory soft-deleted successfully", {
4838
+ function: "executeDeleteMemory",
4839
+ userId,
4840
+ memoryId: memory_id,
4841
+ deletedAt: (/* @__PURE__ */ new Date()).toISOString()
4842
+ });
4843
+ return JSON.stringify(
4844
+ {
4845
+ success: true,
4846
+ memory_id,
4847
+ message: "Memory deleted successfully"
4848
+ },
4849
+ null,
4850
+ 2
4851
+ );
4852
+ } catch (error) {
4853
+ logger.error("Failed to execute delete memory", {
4854
+ function: "executeDeleteMemory",
4855
+ userId,
4856
+ memoryId: request.payload.memory_id,
4857
+ error: error instanceof Error ? error.message : String(error),
4858
+ stack: error instanceof Error ? error.stack : void 0
4859
+ });
4860
+ throw error;
4861
+ }
4862
+ }
4687
4863
 
4688
4864
  // src/tools/deny.ts
4689
4865
  var denyTool = {
@@ -4742,7 +4918,7 @@ async function handleDeny(args, userId) {
4742
4918
  }
4743
4919
 
4744
4920
  // src/tools/search-space.ts
4745
- import { Filters as Filters3 } from "weaviate-client";
4921
+ import { Filters as Filters4 } from "weaviate-client";
4746
4922
  init_space_memory();
4747
4923
  var searchSpaceTool = {
4748
4924
  name: "remember_search_space",
@@ -4885,7 +5061,7 @@ async function handleSearchSpace(args, userId) {
4885
5061
  if (args.date_to) {
4886
5062
  filterList.push(publicCollection.filter.byProperty("created_at").lessOrEqual(new Date(args.date_to)));
4887
5063
  }
4888
- const whereFilter = filterList.length > 0 ? Filters3.and(...filterList) : void 0;
5064
+ const whereFilter = filterList.length > 0 ? Filters4.and(...filterList) : void 0;
4889
5065
  debug.debug("Executing hybrid search", {
4890
5066
  query: args.query,
4891
5067
  filterCount: filterList.length,
@@ -4935,7 +5111,7 @@ async function handleSearchSpace(args, userId) {
4935
5111
  }
4936
5112
 
4937
5113
  // src/tools/query-space.ts
4938
- import { Filters as Filters4 } from "weaviate-client";
5114
+ import { Filters as Filters5 } from "weaviate-client";
4939
5115
  init_space_memory();
4940
5116
  var querySpaceTool = {
4941
5117
  name: "remember_query_space",
@@ -5065,7 +5241,7 @@ async function handleQuerySpace(args, userId) {
5065
5241
  if (args.date_to) {
5066
5242
  filterList.push(publicCollection.filter.byProperty("created_at").lessOrEqual(new Date(args.date_to)));
5067
5243
  }
5068
- const whereFilter = filterList.length > 0 ? Filters4.and(...filterList) : void 0;
5244
+ const whereFilter = filterList.length > 0 ? Filters5.and(...filterList) : void 0;
5069
5245
  debug.debug("Executing semantic query", {
5070
5246
  question: args.question,
5071
5247
  filterCount: filterList.length,