@prmichaelsen/remember-mcp 2.7.11 → 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 (101) hide show
  1. package/.env.example +6 -0
  2. package/AGENT.md +224 -21
  3. package/CHANGELOG.md +155 -915
  4. package/README.md +130 -1
  5. package/agent/commands/acp.command-create.md +372 -0
  6. package/agent/commands/acp.design-create.md +224 -0
  7. package/agent/commands/acp.init.md +39 -5
  8. package/agent/commands/acp.package-create.md +894 -0
  9. package/agent/commands/acp.package-info.md +211 -0
  10. package/agent/commands/acp.package-install.md +206 -33
  11. package/agent/commands/acp.package-list.md +279 -0
  12. package/agent/commands/acp.package-publish.md +540 -0
  13. package/agent/commands/acp.package-remove.md +292 -0
  14. package/agent/commands/acp.package-search.md +306 -0
  15. package/agent/commands/acp.package-update.md +360 -0
  16. package/agent/commands/acp.package-validate.md +539 -0
  17. package/agent/commands/acp.pattern-create.md +326 -0
  18. package/agent/commands/acp.plan.md +552 -0
  19. package/agent/commands/acp.proceed.md +111 -86
  20. package/agent/commands/acp.project-create.md +672 -0
  21. package/agent/commands/acp.project-list.md +224 -0
  22. package/agent/commands/acp.project-set.md +226 -0
  23. package/agent/commands/acp.report.md +2 -0
  24. package/agent/commands/acp.resume.md +237 -0
  25. package/agent/commands/acp.sync.md +55 -15
  26. package/agent/commands/acp.task-create.md +390 -0
  27. package/agent/commands/acp.validate.md +61 -10
  28. package/agent/commands/acp.version-check-for-updates.md +5 -5
  29. package/agent/commands/acp.version-check.md +6 -6
  30. package/agent/commands/acp.version-update.md +6 -6
  31. package/agent/commands/command.template.md +43 -0
  32. package/agent/commands/git.commit.md +5 -3
  33. package/agent/design/soft-delete-system.md +291 -0
  34. package/agent/manifest.template.yaml +13 -0
  35. package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
  36. package/agent/package.template.yaml +36 -0
  37. package/agent/progress.template.yaml +3 -0
  38. package/agent/progress.yaml +238 -6
  39. package/agent/scripts/acp.common.sh +1536 -0
  40. package/agent/scripts/{install.sh → acp.install.sh} +82 -26
  41. package/agent/scripts/acp.package-create.sh +925 -0
  42. package/agent/scripts/acp.package-info.sh +270 -0
  43. package/agent/scripts/acp.package-install.sh +596 -0
  44. package/agent/scripts/acp.package-list.sh +263 -0
  45. package/agent/scripts/acp.package-publish.sh +420 -0
  46. package/agent/scripts/acp.package-remove.sh +272 -0
  47. package/agent/scripts/acp.package-search.sh +156 -0
  48. package/agent/scripts/acp.package-update.sh +438 -0
  49. package/agent/scripts/acp.package-validate.sh +855 -0
  50. package/agent/scripts/acp.project-list.sh +121 -0
  51. package/agent/scripts/acp.project-set.sh +138 -0
  52. package/agent/scripts/{uninstall.sh → acp.uninstall.sh} +25 -15
  53. package/agent/scripts/{check-for-updates.sh → acp.version-check-for-updates.sh} +24 -14
  54. package/agent/scripts/{version.sh → acp.version-check.sh} +20 -8
  55. package/agent/scripts/{update.sh → acp.version-update.sh} +44 -25
  56. package/agent/scripts/acp.yaml-parser.sh +853 -0
  57. package/agent/scripts/acp.yaml-validate.sh +205 -0
  58. package/agent/tasks/task-68-fix-missing-space-properties.md +192 -0
  59. package/agent/tasks/task-69-add-comprehensive-tool-debugging.md +454 -0
  60. package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
  61. package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
  62. package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
  63. package/agent/tasks/task-73-update-relationship-handling.md +18 -0
  64. package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
  65. package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
  66. package/dist/config.d.ts +18 -0
  67. package/dist/server-factory.js +788 -355
  68. package/dist/server.js +788 -355
  69. package/dist/tools/delete-memory.d.ts +5 -30
  70. package/dist/tools/find-similar.d.ts +8 -1
  71. package/dist/tools/query-memory.d.ts +8 -1
  72. package/dist/tools/search-memory.d.ts +6 -0
  73. package/dist/tools/search-relationship.d.ts +8 -1
  74. package/dist/types/memory.d.ts +8 -0
  75. package/dist/types/space-memory.d.ts +3 -0
  76. package/dist/utils/debug.d.ts +52 -0
  77. package/dist/utils/debug.spec.d.ts +5 -0
  78. package/dist/utils/weaviate-filters.d.ts +19 -0
  79. package/dist/weaviate/client.d.ts +1 -1
  80. package/package.json +1 -1
  81. package/src/config.ts +33 -0
  82. package/src/tools/confirm.ts +113 -8
  83. package/src/tools/create-relationship.ts +14 -1
  84. package/src/tools/delete-memory.ts +91 -63
  85. package/src/tools/find-similar.ts +30 -5
  86. package/src/tools/publish.ts +19 -1
  87. package/src/tools/query-memory.ts +18 -5
  88. package/src/tools/query-space.ts +36 -3
  89. package/src/tools/search-memory.ts +18 -5
  90. package/src/tools/search-relationship.ts +19 -5
  91. package/src/tools/search-space.ts +36 -3
  92. package/src/tools/update-memory.ts +8 -0
  93. package/src/types/memory.ts +11 -0
  94. package/src/types/space-memory.ts +5 -0
  95. package/src/utils/debug.spec.ts +257 -0
  96. package/src/utils/debug.ts +138 -0
  97. package/src/utils/weaviate-filters.ts +28 -1
  98. package/src/weaviate/client.ts +47 -3
  99. package/src/weaviate/schema.ts +17 -0
  100. package/src/weaviate/space-schema.spec.ts +5 -2
  101. package/src/weaviate/space-schema.ts +17 -5
package/dist/server.js CHANGED
@@ -479,12 +479,36 @@ function validateConfig() {
479
479
  });
480
480
  });
481
481
  }
482
- var import_dotenv, config;
482
+ var import_dotenv, debugConfig, config;
483
483
  var init_config = __esm({
484
484
  "src/config.ts"() {
485
485
  "use strict";
486
486
  import_dotenv = __toESM(require_main(), 1);
487
487
  import_dotenv.default.config();
488
+ debugConfig = {
489
+ level: (() => {
490
+ const level = process.env.REMEMBER_MCP_DEBUG_LEVEL?.toUpperCase();
491
+ switch (level) {
492
+ case "TRACE":
493
+ return 5 /* TRACE */;
494
+ case "DEBUG":
495
+ return 4 /* DEBUG */;
496
+ case "INFO":
497
+ return 3 /* INFO */;
498
+ case "WARN":
499
+ return 2 /* WARN */;
500
+ case "ERROR":
501
+ return 1 /* ERROR */;
502
+ case "NONE":
503
+ return 0 /* NONE */;
504
+ default:
505
+ return 0 /* NONE */;
506
+ }
507
+ })(),
508
+ enabled: (level) => {
509
+ return debugConfig.level >= level;
510
+ }
511
+ };
488
512
  config = {
489
513
  // Weaviate
490
514
  weaviate: {
@@ -538,6 +562,103 @@ import {
538
562
  init_config();
539
563
  init_logger();
540
564
  import weaviate from "weaviate-client";
565
+
566
+ // src/utils/debug.ts
567
+ init_config();
568
+ init_logger();
569
+ var DebugLogger = class {
570
+ context;
571
+ constructor(context) {
572
+ this.context = context;
573
+ }
574
+ trace(message, data) {
575
+ if (debugConfig.enabled(5 /* TRACE */)) {
576
+ logger.debug(`[TRACE] ${message}`, {
577
+ ...this.context,
578
+ ...data,
579
+ debugLevel: "TRACE"
580
+ });
581
+ }
582
+ }
583
+ debug(message, data) {
584
+ if (debugConfig.enabled(4 /* DEBUG */)) {
585
+ logger.debug(`[DEBUG] ${message}`, {
586
+ ...this.context,
587
+ ...data,
588
+ debugLevel: "DEBUG"
589
+ });
590
+ }
591
+ }
592
+ info(message, data) {
593
+ if (debugConfig.enabled(3 /* INFO */)) {
594
+ logger.info(`[INFO] ${message}`, {
595
+ ...this.context,
596
+ ...data,
597
+ debugLevel: "INFO"
598
+ });
599
+ }
600
+ }
601
+ warn(message, data) {
602
+ if (debugConfig.enabled(2 /* WARN */)) {
603
+ logger.warn(`[WARN] ${message}`, {
604
+ ...this.context,
605
+ ...data,
606
+ debugLevel: "WARN"
607
+ });
608
+ }
609
+ }
610
+ error(message, data) {
611
+ if (debugConfig.enabled(1 /* ERROR */)) {
612
+ logger.error(`[ERROR] ${message}`, {
613
+ ...this.context,
614
+ ...data,
615
+ debugLevel: "ERROR"
616
+ });
617
+ }
618
+ }
619
+ /**
620
+ * Dump full object (TRACE only)
621
+ * Use with caution - may expose sensitive data
622
+ */
623
+ dump(label, obj) {
624
+ if (debugConfig.enabled(5 /* TRACE */)) {
625
+ logger.debug(`[DUMP] ${label}`, {
626
+ ...this.context,
627
+ dump: JSON.stringify(obj, null, 2),
628
+ debugLevel: "TRACE"
629
+ });
630
+ }
631
+ }
632
+ /**
633
+ * Time an async operation (DEBUG and above)
634
+ * Logs start, completion, and duration
635
+ */
636
+ async time(label, fn) {
637
+ if (!debugConfig.enabled(4 /* DEBUG */)) {
638
+ return fn();
639
+ }
640
+ const start = Date.now();
641
+ this.debug(`${label} - Starting`);
642
+ try {
643
+ const result = await fn();
644
+ const duration = Date.now() - start;
645
+ this.debug(`${label} - Completed`, { durationMs: duration });
646
+ return result;
647
+ } catch (error) {
648
+ const duration = Date.now() - start;
649
+ this.error(`${label} - Failed`, {
650
+ durationMs: duration,
651
+ error: error instanceof Error ? error.message : String(error)
652
+ });
653
+ throw error;
654
+ }
655
+ }
656
+ };
657
+ function createDebugLogger(context) {
658
+ return new DebugLogger(context);
659
+ }
660
+
661
+ // src/weaviate/client.ts
541
662
  var client = null;
542
663
  async function initWeaviateClient() {
543
664
  if (client) {
@@ -660,20 +781,56 @@ var ALL_MEMORY_PROPERTIES = [
660
781
  // Comment/threading fields
661
782
  "parent_id",
662
783
  "thread_root_id",
663
- "moderation_flags"
784
+ "moderation_flags",
785
+ // Space/publishing fields (for Memory_public collection)
786
+ "spaces",
787
+ "space_id",
788
+ "author_id",
789
+ "ghost_id",
790
+ "attribution",
791
+ "published_at",
792
+ "discovery_count",
793
+ "space_memory_id",
794
+ // Soft delete fields
795
+ "deleted_at",
796
+ "deleted_by",
797
+ "deletion_reason"
664
798
  ];
665
799
  async function fetchMemoryWithAllProperties(collection, memoryId) {
800
+ const debug = createDebugLogger({
801
+ tool: "weaviate-client",
802
+ operation: "fetchMemoryWithAllProperties"
803
+ });
804
+ debug.debug("Fetching memory", {
805
+ memoryId,
806
+ collectionName: collection.name,
807
+ propertyCount: ALL_MEMORY_PROPERTIES.length
808
+ });
666
809
  try {
667
- return await collection.query.fetchObjectById(memoryId, {
668
- returnProperties: ALL_MEMORY_PROPERTIES
810
+ const result = await debug.time("Fetch with all properties", async () => {
811
+ return await collection.query.fetchObjectById(memoryId, {
812
+ returnProperties: ALL_MEMORY_PROPERTIES
813
+ });
814
+ });
815
+ debug.trace("Fetch result", {
816
+ found: !!result,
817
+ propertyCount: result?.properties ? Object.keys(result.properties).length : 0,
818
+ hasSpaces: !!result?.properties?.spaces,
819
+ hasAuthorId: !!result?.properties?.author_id
669
820
  });
821
+ return result;
670
822
  } catch (error) {
823
+ debug.warn("Fetch with all properties failed, falling back", {
824
+ error: error instanceof Error ? error.message : String(error)
825
+ });
671
826
  logger.warn("Failed to fetch with all properties, falling back to unspecified fetch", {
672
827
  module: "weaviate-client",
673
828
  memoryId,
674
829
  error: error instanceof Error ? error.message : String(error)
675
830
  });
676
- return await collection.query.fetchObjectById(memoryId);
831
+ return await debug.time("Fetch without property specification", async () => {
832
+ return await collection.query.fetchObjectById(memoryId);
833
+ });
677
834
  }
678
835
  }
679
836
 
@@ -975,6 +1132,22 @@ async function createMemoryCollection(userId) {
975
1132
  name: "moderation_flags",
976
1133
  dataType: "text[]",
977
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"
978
1151
  }
979
1152
  ]
980
1153
  });
@@ -1678,6 +1851,14 @@ function combineFiltersWithOr(filters) {
1678
1851
  }
1679
1852
  return Filters.or(...validFilters);
1680
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
+ }
1681
1862
 
1682
1863
  // src/tools/search-memory.ts
1683
1864
  var searchMemoryTool = {
@@ -1778,6 +1959,12 @@ var searchMemoryTool = {
1778
1959
  type: "boolean",
1779
1960
  description: "Include relationships in results. Default: true (searches both memories and relationships)",
1780
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)'
1781
1968
  }
1782
1969
  },
1783
1970
  required: ["query"]
@@ -1798,19 +1985,22 @@ async function handleSearchMemory(args, userId) {
1798
1985
  const alpha = args.alpha ?? 0.7;
1799
1986
  const limit = args.limit ?? 10;
1800
1987
  const offset = args.offset ?? 0;
1801
- 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));
1802
1991
  const searchOptions = {
1803
1992
  alpha,
1804
1993
  limit: limit + offset
1805
1994
  // Get extra for offset
1806
1995
  };
1807
- if (filters) {
1808
- searchOptions.filters = filters;
1996
+ if (combinedFilters) {
1997
+ searchOptions.filters = combinedFilters;
1809
1998
  }
1810
1999
  logger.info("Weaviate query", {
1811
2000
  query: args.query,
1812
2001
  searchOptions: JSON.stringify(searchOptions, null, 2),
1813
- hasFilters: !!filters
2002
+ hasFilters: !!combinedFilters,
2003
+ deletedFilter: args.deleted_filter || "exclude"
1814
2004
  });
1815
2005
  const results = await collection.query.hybrid(args.query, searchOptions);
1816
2006
  const paginatedResults = results.objects.slice(offset);
@@ -1854,95 +2044,360 @@ async function handleSearchMemory(args, userId) {
1854
2044
  }
1855
2045
 
1856
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";
1857
2051
  init_logger();
1858
- var deleteMemoryTool = {
1859
- name: "remember_delete_memory",
1860
- description: `Delete a memory from your collection.
1861
-
1862
- Optionally delete connected relationships as well.
1863
- This action cannot be undone.
1864
-
1865
- Examples:
1866
- - "Delete that old camping note"
1867
- - "Remove the recipe I saved yesterday"
1868
- `,
1869
- inputSchema: {
1870
- type: "object",
1871
- properties: {
1872
- memory_id: {
1873
- type: "string",
1874
- description: "ID of the memory to delete"
1875
- },
1876
- delete_relationships: {
1877
- type: "boolean",
1878
- description: "Also delete connected relationships. Default: false",
1879
- 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;
1880
2106
  }
1881
- },
1882
- required: ["memory_id"]
1883
- }
1884
- };
1885
- async function handleDeleteMemory(args, userId) {
1886
- try {
1887
- logger.info("Deleting memory", { userId, memoryId: args.memory_id });
1888
- const collection = getMemoryCollection(userId);
1889
- const memory = await collection.query.fetchObjectById(args.memory_id, {
1890
- returnProperties: ["user_id", "doc_type", "relationships"]
1891
- });
1892
- if (!memory) {
1893
- throw new Error(`Memory not found: ${args.memory_id}`);
1894
- }
1895
- if (memory.properties.user_id !== userId) {
1896
- throw new Error("Unauthorized: Cannot delete another user's memory");
1897
- }
1898
- if (memory.properties.doc_type !== "memory") {
1899
- throw new Error("Cannot delete relationships using this tool. Use remember_delete_relationship instead.");
1900
- }
1901
- let relationshipsDeleted = 0;
1902
- if (args.delete_relationships && memory.properties.relationships) {
1903
- const relationshipIds = memory.properties.relationships;
1904
- for (const relId of relationshipIds) {
1905
- try {
1906
- await collection.data.deleteById(relId);
1907
- relationshipsDeleted++;
1908
- } catch (error) {
1909
- logger.warn(`Failed to delete relationship ${relId}:`, error);
1910
- }
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;
1911
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;
1912
2134
  }
1913
- await collection.data.deleteById(args.memory_id);
1914
- 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",
1915
2147
  userId,
1916
- memoryId: args.memory_id,
1917
- relationshipsDeleted
2148
+ token,
2149
+ collectionPath
1918
2150
  });
1919
- const result = {
1920
- memory_id: args.memory_id,
1921
- deleted: true,
1922
- relationships_deleted: relationshipsDeleted,
1923
- 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
1924
2157
  };
1925
- return JSON.stringify(result, null, 2);
1926
- } catch (error) {
1927
- handleToolError(error, {
1928
- toolName: "remember_delete_memory",
1929
- operation: "delete memory",
1930
- userId,
1931
- memoryId: args.memory_id,
1932
- 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
1933
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
+ };
1934
2194
  }
1935
- }
1936
-
1937
- // src/tools/update-memory.ts
1938
- init_logger();
1939
- var updateMemoryTool = {
1940
- name: "remember_update_memory",
1941
- description: `Update an existing memory with partial updates.
1942
-
1943
- Supports updating any field except id, user_id, doc_type, created_at.
1944
- Version number is automatically incremented and updated_at is set.
1945
- Only provided fields are updated (partial updates supported).
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).
1946
2401
 
1947
2402
  Examples:
1948
2403
  - "Update that camping note to add more details"
@@ -2037,6 +2492,10 @@ async function handleUpdateMemory(args, userId) {
2037
2492
  if (existingMemory.properties.doc_type !== "memory") {
2038
2493
  throw new Error("Cannot update relationships using this tool. Use remember_update_relationship instead.");
2039
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
+ }
2040
2499
  const updates = {};
2041
2500
  const updatedFields = [];
2042
2501
  if (args.content !== void 0) {
@@ -2204,6 +2663,12 @@ var findSimilarTool = {
2204
2663
  type: "boolean",
2205
2664
  description: "Include relationships in results. Default: false",
2206
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)'
2207
2672
  }
2208
2673
  }
2209
2674
  }
@@ -2220,6 +2685,7 @@ async function handleFindSimilar(args, userId) {
2220
2685
  const collection = getMemoryCollection(userId);
2221
2686
  const limit = args.limit ?? 10;
2222
2687
  const minSimilarity = args.min_similarity ?? 0.7;
2688
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
2223
2689
  let results;
2224
2690
  if (args.memory_id) {
2225
2691
  const memory = await collection.query.fetchObjectById(args.memory_id, {
@@ -2234,20 +2700,28 @@ async function handleFindSimilar(args, userId) {
2234
2700
  if (memory.properties.doc_type !== "memory") {
2235
2701
  throw new Error("Can only find similar memories for memory documents, not relationships");
2236
2702
  }
2237
- results = await collection.query.nearObject(args.memory_id, {
2703
+ const searchOptions = {
2238
2704
  limit: limit + 1,
2239
2705
  // +1 to exclude the source memory itself
2240
2706
  distance: 1 - minSimilarity,
2241
2707
  // Convert similarity to distance
2242
2708
  returnMetadata: ["distance"]
2243
- });
2709
+ };
2710
+ if (deletedFilter) {
2711
+ searchOptions.filters = deletedFilter;
2712
+ }
2713
+ results = await collection.query.nearObject(args.memory_id, searchOptions);
2244
2714
  results.objects = results.objects.filter((obj) => obj.uuid !== args.memory_id);
2245
2715
  } else {
2246
- results = await collection.query.nearText(args.text, {
2716
+ const searchOptions = {
2247
2717
  limit,
2248
2718
  distance: 1 - minSimilarity,
2249
2719
  returnMetadata: ["distance"]
2250
- });
2720
+ };
2721
+ if (deletedFilter) {
2722
+ searchOptions.filters = deletedFilter;
2723
+ }
2724
+ results = await collection.query.nearText(args.text, searchOptions);
2251
2725
  }
2252
2726
  if (!args.include_relationships) {
2253
2727
  results.objects = results.objects.filter(
@@ -2394,6 +2868,12 @@ var queryMemoryTool = {
2394
2868
  description: 'Output format: "detailed" (full objects) or "compact" (text summary). Default: detailed',
2395
2869
  enum: ["detailed", "compact"],
2396
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)'
2397
2877
  }
2398
2878
  },
2399
2879
  required: ["query"]
@@ -2410,15 +2890,17 @@ async function handleQueryMemory(args, userId) {
2410
2890
  const minRelevance = args.min_relevance ?? 0.6;
2411
2891
  const includeContext = args.include_context ?? true;
2412
2892
  const format = args.format ?? "detailed";
2413
- 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));
2414
2896
  const searchOptions = {
2415
2897
  limit,
2416
2898
  distance: 1 - minRelevance,
2417
2899
  // Convert relevance to distance
2418
2900
  returnMetadata: ["distance"]
2419
2901
  };
2420
- if (filters) {
2421
- searchOptions.filters = filters;
2902
+ if (combinedFilters) {
2903
+ searchOptions.filters = combinedFilters;
2422
2904
  }
2423
2905
  const results = await collection.query.nearText(args.query, searchOptions);
2424
2906
  const relevantMemories = results.objects.map((obj) => {
@@ -2549,7 +3031,7 @@ async function handleCreateRelationship(args, userId, context) {
2549
3031
  args.memory_ids.map(async (memoryId) => {
2550
3032
  try {
2551
3033
  const memory = await collection.query.fetchObjectById(memoryId, {
2552
- returnProperties: ["user_id", "doc_type", "relationships"]
3034
+ returnProperties: ["user_id", "doc_type", "relationships", "deleted_at"]
2553
3035
  });
2554
3036
  if (!memory) {
2555
3037
  logger.warn("Memory not found", { userId, memoryId });
@@ -2571,6 +3053,15 @@ async function handleCreateRelationship(args, userId, context) {
2571
3053
  });
2572
3054
  return { memoryId, error: "Cannot create relationship with non-memory document" };
2573
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
+ }
2574
3065
  return {
2575
3066
  memoryId,
2576
3067
  memory,
@@ -2817,7 +3308,7 @@ async function handleUpdateRelationship(args, userId) {
2817
3308
  }
2818
3309
 
2819
3310
  // src/tools/search-relationship.ts
2820
- import { Filters as Filters2 } from "weaviate-client";
3311
+ import { Filters as Filters3 } from "weaviate-client";
2821
3312
  init_logger();
2822
3313
  var searchRelationshipTool = {
2823
3314
  name: "remember_search_relationship",
@@ -2871,6 +3362,12 @@ var searchRelationshipTool = {
2871
3362
  type: "number",
2872
3363
  description: "Offset for pagination (default: 0)",
2873
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)'
2874
3371
  }
2875
3372
  },
2876
3373
  required: ["query"]
@@ -2886,7 +3383,11 @@ async function handleSearchRelationship(args, userId) {
2886
3383
  const collection = getMemoryCollection(userId);
2887
3384
  const limit = args.limit ?? 10;
2888
3385
  const offset = args.offset ?? 0;
3386
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
2889
3387
  const filterList = [];
3388
+ if (deletedFilter) {
3389
+ filterList.push(deletedFilter);
3390
+ }
2890
3391
  filterList.push(
2891
3392
  collection.filter.byProperty("doc_type").equal("relationship")
2892
3393
  );
@@ -2899,7 +3400,7 @@ async function handleSearchRelationship(args, userId) {
2899
3400
  const typeFilters = args.relationship_types.map(
2900
3401
  (type) => collection.filter.byProperty("relationship_type").equal(type)
2901
3402
  );
2902
- filterList.push(Filters2.or(...typeFilters));
3403
+ filterList.push(Filters3.or(...typeFilters));
2903
3404
  }
2904
3405
  }
2905
3406
  if (args.strength_min !== void 0) {
@@ -2917,7 +3418,7 @@ async function handleSearchRelationship(args, userId) {
2917
3418
  collection.filter.byProperty("tags").containsAny(args.tags)
2918
3419
  );
2919
3420
  }
2920
- const combinedFilters = filterList.length > 1 ? Filters2.and(...filterList) : filterList[0];
3421
+ const combinedFilters = combineFiltersWithAnd(filterList);
2921
3422
  const searchOptions = {
2922
3423
  alpha: 1,
2923
3424
  // Pure semantic search for relationships
@@ -3519,238 +4020,6 @@ async function handleGetPreferences(args, userId) {
3519
4020
  }
3520
4021
  }
3521
4022
 
3522
- // src/services/confirmation-token.service.ts
3523
- import { randomUUID } from "crypto";
3524
- init_logger();
3525
- var ConfirmationTokenService = class {
3526
- EXPIRY_MINUTES = 5;
3527
- /**
3528
- * Create a new confirmation request
3529
- *
3530
- * @param userId - User ID who initiated the request
3531
- * @param action - Action type (e.g., 'publish_memory')
3532
- * @param payload - Data to store with the request
3533
- * @param targetCollection - Optional target collection (e.g., 'the_void')
3534
- * @returns Request ID and token
3535
- */
3536
- async createRequest(userId, action, payload, targetCollection) {
3537
- try {
3538
- const token = randomUUID();
3539
- const now = /* @__PURE__ */ new Date();
3540
- const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
3541
- const request = {
3542
- user_id: userId,
3543
- token,
3544
- action,
3545
- target_collection: targetCollection,
3546
- payload,
3547
- created_at: now.toISOString(),
3548
- expires_at: expiresAt.toISOString(),
3549
- status: "pending"
3550
- };
3551
- const collectionPath = `users/${userId}/requests`;
3552
- logger.info("Creating confirmation request", {
3553
- service: "ConfirmationTokenService",
3554
- userId,
3555
- action,
3556
- targetCollection,
3557
- collectionPath,
3558
- payloadKeys: Object.keys(payload)
3559
- });
3560
- logger.debug("Calling Firestore addDocument", {
3561
- service: "ConfirmationTokenService",
3562
- collectionPath
3563
- });
3564
- const docRef = await addDocument(collectionPath, request);
3565
- logger.debug("Firestore addDocument returned", {
3566
- service: "ConfirmationTokenService",
3567
- hasDocRef: !!docRef,
3568
- hasId: !!docRef?.id,
3569
- docRefId: docRef?.id
3570
- });
3571
- if (!docRef) {
3572
- const error = new Error("Firestore addDocument returned null/undefined");
3573
- logger.error("CRITICAL: addDocument returned null", {
3574
- service: "ConfirmationTokenService",
3575
- userId,
3576
- collectionPath
3577
- });
3578
- throw error;
3579
- }
3580
- if (!docRef.id) {
3581
- const error = new Error("Firestore addDocument returned docRef without ID");
3582
- logger.error("CRITICAL: docRef has no ID", {
3583
- service: "ConfirmationTokenService",
3584
- userId,
3585
- collectionPath,
3586
- docRef
3587
- });
3588
- throw error;
3589
- }
3590
- logger.info("Confirmation request created successfully", {
3591
- service: "ConfirmationTokenService",
3592
- requestId: docRef.id,
3593
- token,
3594
- expiresAt: request.expires_at
3595
- });
3596
- return { requestId: docRef.id, token };
3597
- } catch (error) {
3598
- logger.error("Failed to create confirmation request", {
3599
- service: "ConfirmationTokenService",
3600
- error: error instanceof Error ? error.message : String(error),
3601
- stack: error instanceof Error ? error.stack : void 0,
3602
- userId,
3603
- action,
3604
- collectionPath: `users/${userId}/requests`
3605
- });
3606
- throw error;
3607
- }
3608
- }
3609
- /**
3610
- * Validate and retrieve a confirmation request
3611
- *
3612
- * @param userId - User ID
3613
- * @param token - Confirmation token
3614
- * @returns Request with request_id if valid, null otherwise
3615
- */
3616
- async validateToken(userId, token) {
3617
- const collectionPath = `users/${userId}/requests`;
3618
- logger.debug("Validating confirmation token", {
3619
- service: "ConfirmationTokenService",
3620
- userId,
3621
- token,
3622
- collectionPath
3623
- });
3624
- const queryOptions = {
3625
- where: [
3626
- { field: "token", op: "==", value: token },
3627
- { field: "status", op: "==", value: "pending" }
3628
- ],
3629
- limit: 1
3630
- };
3631
- const results = await queryDocuments(collectionPath, queryOptions);
3632
- logger.debug("Token query results", {
3633
- service: "ConfirmationTokenService",
3634
- resultsFound: results.length,
3635
- hasResults: results.length > 0
3636
- });
3637
- if (results.length === 0) {
3638
- logger.info("Token not found or not pending", {
3639
- service: "ConfirmationTokenService",
3640
- userId
3641
- });
3642
- return null;
3643
- }
3644
- const doc = results[0];
3645
- const request = doc.data;
3646
- logger.info("Confirmation request found", {
3647
- service: "ConfirmationTokenService",
3648
- requestId: doc.id,
3649
- action: request.action,
3650
- status: request.status,
3651
- expiresAt: request.expires_at
3652
- });
3653
- const expiresAt = new Date(request.expires_at);
3654
- if (expiresAt.getTime() < Date.now()) {
3655
- logger.info("Token expired", {
3656
- service: "ConfirmationTokenService",
3657
- requestId: doc.id,
3658
- expiresAt: request.expires_at
3659
- });
3660
- await this.updateStatus(userId, doc.id, "expired");
3661
- return null;
3662
- }
3663
- return {
3664
- ...request,
3665
- request_id: doc.id
3666
- };
3667
- }
3668
- /**
3669
- * Confirm a request
3670
- *
3671
- * @param userId - User ID
3672
- * @param token - Confirmation token
3673
- * @returns Confirmed request if valid, null otherwise
3674
- */
3675
- async confirmRequest(userId, token) {
3676
- const request = await this.validateToken(userId, token);
3677
- if (!request) {
3678
- return null;
3679
- }
3680
- await this.updateStatus(userId, request.request_id, "confirmed");
3681
- return {
3682
- ...request,
3683
- status: "confirmed",
3684
- confirmed_at: (/* @__PURE__ */ new Date()).toISOString()
3685
- };
3686
- }
3687
- /**
3688
- * Deny a request
3689
- *
3690
- * @param userId - User ID
3691
- * @param token - Confirmation token
3692
- * @returns True if denied successfully, false otherwise
3693
- */
3694
- async denyRequest(userId, token) {
3695
- const request = await this.validateToken(userId, token);
3696
- if (!request) {
3697
- return false;
3698
- }
3699
- await this.updateStatus(userId, request.request_id, "denied");
3700
- return true;
3701
- }
3702
- /**
3703
- * Retract a request
3704
- *
3705
- * @param userId - User ID
3706
- * @param token - Confirmation token
3707
- * @returns True if retracted successfully, false otherwise
3708
- */
3709
- async retractRequest(userId, token) {
3710
- const request = await this.validateToken(userId, token);
3711
- if (!request) {
3712
- return false;
3713
- }
3714
- await this.updateStatus(userId, request.request_id, "retracted");
3715
- return true;
3716
- }
3717
- /**
3718
- * Update request status
3719
- *
3720
- * @param userId - User ID
3721
- * @param requestId - Request document ID
3722
- * @param status - New status
3723
- */
3724
- async updateStatus(userId, requestId, status) {
3725
- const collectionPath = `users/${userId}/requests`;
3726
- const updateData = {
3727
- status
3728
- };
3729
- if (status === "confirmed") {
3730
- updateData.confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
3731
- }
3732
- await updateDocument(collectionPath, requestId, updateData);
3733
- }
3734
- /**
3735
- * Clean up expired requests (optional - Firestore TTL handles deletion)
3736
- *
3737
- * Note: Configure Firestore TTL policy on 'requests' collection group
3738
- * with 'expires_at' field for automatic deletion within 24 hours.
3739
- *
3740
- * This method is optional for immediate cleanup if needed.
3741
- *
3742
- * @returns Count of deleted requests
3743
- */
3744
- async cleanupExpired() {
3745
- logger.warn("cleanupExpired not implemented - relying on Firestore TTL", {
3746
- service: "ConfirmationTokenService",
3747
- note: "Configure Firestore TTL policy on requests collection group"
3748
- });
3749
- return 0;
3750
- }
3751
- };
3752
- var confirmationTokenService = new ConfirmationTokenService();
3753
-
3754
4023
  // src/weaviate/space-schema.ts
3755
4024
  init_space_memory();
3756
4025
  init_logger();
@@ -3791,11 +4060,6 @@ async function createSpaceCollection(client2, spaceId) {
3791
4060
  dataType: "text[]",
3792
4061
  description: 'Spaces this memory is published to (e.g., ["the_void", "dogs"])'
3793
4062
  },
3794
- {
3795
- name: "space_id",
3796
- dataType: "text",
3797
- description: "DEPRECATED: Use spaces array instead. Will be removed in v3.0.0."
3798
- },
3799
4063
  {
3800
4064
  name: "author_id",
3801
4065
  dataType: "text",
@@ -4020,6 +4284,22 @@ async function createSpaceCollection(client2, spaceId) {
4020
4284
  name: "moderation_flags",
4021
4285
  dataType: "text[]",
4022
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"
4023
4303
  }
4024
4304
  ]
4025
4305
  });
@@ -4073,7 +4353,14 @@ var publishTool = {
4073
4353
  }
4074
4354
  };
4075
4355
  async function handlePublish(args, userId) {
4356
+ const debug = createDebugLogger({
4357
+ tool: "remember_publish",
4358
+ userId,
4359
+ operation: "publish_request"
4360
+ });
4076
4361
  try {
4362
+ debug.info("Tool invoked");
4363
+ debug.trace("Arguments", { args });
4077
4364
  logger.info("Starting publish request", {
4078
4365
  tool: "remember_publish",
4079
4366
  userId,
@@ -4082,8 +4369,10 @@ async function handlePublish(args, userId) {
4082
4369
  spaceCount: args.spaces.length,
4083
4370
  additionalTags: args.additional_tags?.length || 0
4084
4371
  });
4372
+ debug.debug("Validating space IDs", { spaces: args.spaces });
4085
4373
  const invalidSpaces = args.spaces.filter((s) => !isValidSpaceId(s));
4086
4374
  if (invalidSpaces.length > 0) {
4375
+ debug.warn("Invalid space IDs detected", { invalidSpaces });
4087
4376
  logger.warn("Invalid space IDs provided", {
4088
4377
  tool: "remember_publish",
4089
4378
  invalidSpaces,
@@ -4127,7 +4416,9 @@ async function handlePublish(args, userId) {
4127
4416
  memoryId: args.memory_id
4128
4417
  });
4129
4418
  const userCollection = weaviateClient.collections.get(collectionName);
4130
- const memory = await fetchMemoryWithAllProperties(userCollection, args.memory_id);
4419
+ const memory = await debug.time("Fetch memory from user collection", async () => {
4420
+ return await fetchMemoryWithAllProperties(userCollection, args.memory_id);
4421
+ });
4131
4422
  logger.debug("Memory fetch result", {
4132
4423
  tool: "remember_publish",
4133
4424
  found: !!memory,
@@ -4222,6 +4513,10 @@ async function handlePublish(args, userId) {
4222
4513
  2
4223
4514
  );
4224
4515
  } catch (error) {
4516
+ debug.error("Tool failed", {
4517
+ error: error instanceof Error ? error.message : String(error),
4518
+ stack: error instanceof Error ? error.stack : void 0
4519
+ });
4225
4520
  return handleToolError(error, {
4226
4521
  toolName: "remember_publish",
4227
4522
  userId,
@@ -4259,13 +4554,23 @@ Violating these requirements bypasses user consent and is a security violation.`
4259
4554
  }
4260
4555
  };
4261
4556
  async function handleConfirm(args, userId) {
4557
+ const debug = createDebugLogger({
4558
+ tool: "remember_confirm",
4559
+ userId,
4560
+ operation: "confirm_action"
4561
+ });
4262
4562
  try {
4563
+ debug.info("Tool invoked");
4564
+ debug.trace("Arguments", { token: args.token });
4263
4565
  logger.info("Starting confirmation", {
4264
4566
  tool: "remember_confirm",
4265
4567
  userId,
4266
4568
  token: args.token
4267
4569
  });
4268
- const request = await confirmationTokenService.confirmRequest(userId, args.token);
4570
+ debug.debug("Validating confirmation token");
4571
+ const request = await debug.time("Confirm token", async () => {
4572
+ return await confirmationTokenService.confirmRequest(userId, args.token);
4573
+ });
4269
4574
  logger.debug("Token validation result", {
4270
4575
  tool: "remember_confirm",
4271
4576
  requestFound: !!request,
@@ -4294,8 +4599,15 @@ async function handleConfirm(args, userId) {
4294
4599
  if (request.action === "publish_memory") {
4295
4600
  return await executePublishMemory(request, userId);
4296
4601
  }
4602
+ if (request.action === "delete_memory") {
4603
+ return await executeDeleteMemory(request, userId);
4604
+ }
4297
4605
  throw new Error(`Unknown action type: ${request.action}`);
4298
4606
  } catch (error) {
4607
+ debug.error("Tool failed", {
4608
+ error: error instanceof Error ? error.message : String(error),
4609
+ stack: error instanceof Error ? error.stack : void 0
4610
+ });
4299
4611
  handleToolError(error, {
4300
4612
  toolName: "remember_confirm",
4301
4613
  userId,
@@ -4305,7 +4617,16 @@ async function handleConfirm(args, userId) {
4305
4617
  }
4306
4618
  }
4307
4619
  async function executePublishMemory(request, userId) {
4620
+ const debug = createDebugLogger({
4621
+ tool: "remember_confirm",
4622
+ userId,
4623
+ operation: "execute_publish"
4624
+ });
4308
4625
  try {
4626
+ debug.debug("Executing publish memory action", {
4627
+ memoryId: request.payload.memory_id,
4628
+ spaces: request.payload.spaces
4629
+ });
4309
4630
  logger.info("Executing publish memory action", {
4310
4631
  function: "executePublishMemory",
4311
4632
  userId,
@@ -4322,10 +4643,12 @@ async function executePublishMemory(request, userId) {
4322
4643
  collectionName: getMemoryCollectionName(userId),
4323
4644
  memoryId: request.payload.memory_id
4324
4645
  });
4325
- const originalMemory = await fetchMemoryWithAllProperties(
4326
- userCollection,
4327
- request.payload.memory_id
4328
- );
4646
+ const originalMemory = await debug.time("Fetch original memory", async () => {
4647
+ return await fetchMemoryWithAllProperties(
4648
+ userCollection,
4649
+ request.payload.memory_id
4650
+ );
4651
+ });
4329
4652
  logger.info("Original memory fetch result", {
4330
4653
  function: "executePublishMemory",
4331
4654
  found: !!originalMemory,
@@ -4435,14 +4758,20 @@ async function executePublishMemory(request, userId) {
4435
4758
  contentLength: publishedMemory.content?.length || 0,
4436
4759
  titleValue: publishedMemory.title || "NO_TITLE"
4437
4760
  });
4438
- const result = await publicCollection.data.insert({
4439
- properties: publishedMemory
4761
+ const result = await debug.time("Insert into Memory_public", async () => {
4762
+ return await publicCollection.data.insert({
4763
+ properties: publishedMemory
4764
+ });
4440
4765
  });
4441
4766
  logger.info("Memory published successfully", {
4442
4767
  function: "executePublishMemory",
4443
4768
  spaceMemoryId: result,
4444
4769
  spaces: request.payload.spaces
4445
4770
  });
4771
+ debug.info("Memory published successfully", {
4772
+ spaceMemoryId: result,
4773
+ spaces: request.payload.spaces
4774
+ });
4446
4775
  try {
4447
4776
  await userCollection.data.update({
4448
4777
  id: request.payload.memory_id,
@@ -4473,6 +4802,10 @@ async function executePublishMemory(request, userId) {
4473
4802
  2
4474
4803
  );
4475
4804
  } catch (error) {
4805
+ debug.error("Execute publish failed", {
4806
+ error: error instanceof Error ? error.message : String(error),
4807
+ stack: error instanceof Error ? error.stack : void 0
4808
+ });
4476
4809
  handleToolError(error, {
4477
4810
  toolName: "remember_confirm",
4478
4811
  userId,
@@ -4481,6 +4814,52 @@ async function executePublishMemory(request, userId) {
4481
4814
  });
4482
4815
  }
4483
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
+ }
4484
4863
 
4485
4864
  // src/tools/deny.ts
4486
4865
  var denyTool = {
@@ -4539,7 +4918,7 @@ async function handleDeny(args, userId) {
4539
4918
  }
4540
4919
 
4541
4920
  // src/tools/search-space.ts
4542
- import { Filters as Filters3 } from "weaviate-client";
4921
+ import { Filters as Filters4 } from "weaviate-client";
4543
4922
  init_space_memory();
4544
4923
  var searchSpaceTool = {
4545
4924
  name: "remember_search_space",
@@ -4617,7 +4996,15 @@ Let the search algorithm find ALL relevant memories regardless of type unless ex
4617
4996
  }
4618
4997
  };
4619
4998
  async function handleSearchSpace(args, userId) {
4999
+ const debug = createDebugLogger({
5000
+ tool: "remember_search_space",
5001
+ userId,
5002
+ operation: "search_spaces"
5003
+ });
4620
5004
  try {
5005
+ debug.info("Tool invoked");
5006
+ debug.trace("Arguments", { args });
5007
+ debug.debug("Validating space IDs", { spaces: args.spaces });
4621
5008
  const invalidSpaces = args.spaces.filter((s) => !isValidSpaceId(s));
4622
5009
  if (invalidSpaces.length > 0) {
4623
5010
  return JSON.stringify(
@@ -4674,11 +5061,22 @@ async function handleSearchSpace(args, userId) {
4674
5061
  if (args.date_to) {
4675
5062
  filterList.push(publicCollection.filter.byProperty("created_at").lessOrEqual(new Date(args.date_to)));
4676
5063
  }
4677
- const whereFilter = filterList.length > 0 ? Filters3.and(...filterList) : void 0;
4678
- const searchResults = await publicCollection.query.hybrid(args.query, {
5064
+ const whereFilter = filterList.length > 0 ? Filters4.and(...filterList) : void 0;
5065
+ debug.debug("Executing hybrid search", {
5066
+ query: args.query,
5067
+ filterCount: filterList.length,
4679
5068
  limit: args.limit || 10,
4680
- offset: args.offset || 0,
4681
- ...whereFilter && { where: whereFilter }
5069
+ offset: args.offset || 0
5070
+ });
5071
+ const searchResults = await debug.time("Hybrid search query", async () => {
5072
+ return await publicCollection.query.hybrid(args.query, {
5073
+ limit: args.limit || 10,
5074
+ offset: args.offset || 0,
5075
+ ...whereFilter && { where: whereFilter }
5076
+ });
5077
+ });
5078
+ debug.debug("Search completed", {
5079
+ resultCount: searchResults.objects.length
4682
5080
  });
4683
5081
  const memories = searchResults.objects.map((obj) => ({
4684
5082
  id: obj.uuid,
@@ -4693,8 +5091,16 @@ async function handleSearchSpace(args, userId) {
4693
5091
  offset: args.offset || 0,
4694
5092
  limit: args.limit || 10
4695
5093
  };
5094
+ debug.info("Tool completed successfully", {
5095
+ resultCount: memories.length,
5096
+ spaces: args.spaces
5097
+ });
4696
5098
  return JSON.stringify(result, null, 2);
4697
5099
  } catch (error) {
5100
+ debug.error("Tool failed", {
5101
+ error: error instanceof Error ? error.message : String(error),
5102
+ stack: error instanceof Error ? error.stack : void 0
5103
+ });
4698
5104
  return handleToolError(error, {
4699
5105
  toolName: "remember_search_space",
4700
5106
  operation: "search spaces",
@@ -4705,7 +5111,7 @@ async function handleSearchSpace(args, userId) {
4705
5111
  }
4706
5112
 
4707
5113
  // src/tools/query-space.ts
4708
- import { Filters as Filters4 } from "weaviate-client";
5114
+ import { Filters as Filters5 } from "weaviate-client";
4709
5115
  init_space_memory();
4710
5116
  var querySpaceTool = {
4711
5117
  name: "remember_query_space",
@@ -4778,7 +5184,15 @@ Let the query algorithm find ALL relevant memories regardless of type unless exp
4778
5184
  }
4779
5185
  };
4780
5186
  async function handleQuerySpace(args, userId) {
5187
+ const debug = createDebugLogger({
5188
+ tool: "remember_query_space",
5189
+ userId,
5190
+ operation: "query_spaces"
5191
+ });
4781
5192
  try {
5193
+ debug.info("Tool invoked");
5194
+ debug.trace("Arguments", { args });
5195
+ debug.debug("Validating space IDs", { spaces: args.spaces });
4782
5196
  const invalidSpaces = args.spaces.filter((s) => !isValidSpaceId(s));
4783
5197
  if (invalidSpaces.length > 0) {
4784
5198
  return JSON.stringify(
@@ -4827,10 +5241,21 @@ async function handleQuerySpace(args, userId) {
4827
5241
  if (args.date_to) {
4828
5242
  filterList.push(publicCollection.filter.byProperty("created_at").lessOrEqual(new Date(args.date_to)));
4829
5243
  }
4830
- const whereFilter = filterList.length > 0 ? Filters4.and(...filterList) : void 0;
4831
- const searchResults = await publicCollection.query.nearText(args.question, {
4832
- limit: args.limit || 10,
4833
- ...whereFilter && { where: whereFilter }
5244
+ const whereFilter = filterList.length > 0 ? Filters5.and(...filterList) : void 0;
5245
+ debug.debug("Executing semantic query", {
5246
+ question: args.question,
5247
+ filterCount: filterList.length,
5248
+ limit: args.limit || 10
5249
+ });
5250
+ const searchResults = await debug.time("Semantic query", async () => {
5251
+ return await publicCollection.query.nearText(args.question, {
5252
+ limit: args.limit || 10,
5253
+ ...whereFilter && { where: whereFilter }
5254
+ });
5255
+ });
5256
+ debug.debug("Query completed", {
5257
+ resultCount: searchResults.objects.length,
5258
+ format: args.format || "detailed"
4834
5259
  });
4835
5260
  const format = args.format || "detailed";
4836
5261
  if (format === "compact") {
@@ -4859,9 +5284,17 @@ async function handleQuerySpace(args, userId) {
4859
5284
  memories,
4860
5285
  total: memories.length
4861
5286
  };
5287
+ debug.info("Tool completed successfully", {
5288
+ resultCount: memories.length,
5289
+ format: "detailed"
5290
+ });
4862
5291
  return JSON.stringify(result, null, 2);
4863
5292
  }
4864
5293
  } catch (error) {
5294
+ debug.error("Tool failed", {
5295
+ error: error instanceof Error ? error.message : String(error),
5296
+ stack: error instanceof Error ? error.stack : void 0
5297
+ });
4865
5298
  return handleToolError(error, {
4866
5299
  toolName: "remember_query_space",
4867
5300
  operation: "query spaces",