@prmichaelsen/remember-mcp 3.16.0 → 3.17.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.
@@ -1458,6 +1458,18 @@ function unsafeStringify(arr, offset = 0) {
1458
1458
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
1459
1459
  }
1460
1460
 
1461
+ // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/rng.js
1462
+ import { randomFillSync } from "crypto";
1463
+ var rnds8Pool = new Uint8Array(256);
1464
+ var poolPtr = rnds8Pool.length;
1465
+ function rng() {
1466
+ if (poolPtr > rnds8Pool.length - 16) {
1467
+ randomFillSync(rnds8Pool);
1468
+ poolPtr = 0;
1469
+ }
1470
+ return rnds8Pool.slice(poolPtr, poolPtr += 16);
1471
+ }
1472
+
1461
1473
  // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/v35.js
1462
1474
  function stringToBytes(str) {
1463
1475
  str = unescape(encodeURIComponent(str));
@@ -1494,6 +1506,36 @@ function v35(version, hash, value, namespace, buf, offset) {
1494
1506
  return unsafeStringify(bytes);
1495
1507
  }
1496
1508
 
1509
+ // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/native.js
1510
+ import { randomUUID } from "crypto";
1511
+ var native_default = { randomUUID };
1512
+
1513
+ // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/v4.js
1514
+ function v4(options, buf, offset) {
1515
+ if (native_default.randomUUID && !buf && !options) {
1516
+ return native_default.randomUUID();
1517
+ }
1518
+ options = options || {};
1519
+ const rnds = options.random ?? options.rng?.() ?? rng();
1520
+ if (rnds.length < 16) {
1521
+ throw new Error("Random bytes length must be >= 16");
1522
+ }
1523
+ rnds[6] = rnds[6] & 15 | 64;
1524
+ rnds[8] = rnds[8] & 63 | 128;
1525
+ if (buf) {
1526
+ offset = offset || 0;
1527
+ if (offset < 0 || offset + 16 > buf.length) {
1528
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
1529
+ }
1530
+ for (let i = 0; i < 16; ++i) {
1531
+ buf[offset + i] = rnds[i];
1532
+ }
1533
+ return buf;
1534
+ }
1535
+ return unsafeStringify(rnds);
1536
+ }
1537
+ var v4_default = v4;
1538
+
1497
1539
  // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/sha1.js
1498
1540
  import { createHash } from "crypto";
1499
1541
  function sha1(bytes) {
@@ -1712,6 +1754,9 @@ function buildDocTypeFilters(collection, docType, filters) {
1712
1754
  if (filters?.tags && filters.tags.length > 0) {
1713
1755
  filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
1714
1756
  }
1757
+ if (filters?.is_user_organized !== void 0) {
1758
+ filterList.push(collection.filter.byProperty("is_user_organized").equal(filters.is_user_organized));
1759
+ }
1715
1760
  if (filters?.memory_ids && filters.memory_ids.length > 0) {
1716
1761
  filterList.push(collection.filter.byId().containsAny(filters.memory_ids));
1717
1762
  }
@@ -1871,6 +1916,7 @@ var COMMON_MEMORY_PROPERTIES = [
1871
1916
  { name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
1872
1917
  { name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
1873
1918
  { name: "relationship_count", dataType: configure.dataType.INT },
1919
+ { name: "member_count", dataType: configure.dataType.INT },
1874
1920
  // Rating aggregates (denormalized from Firestore individual ratings)
1875
1921
  { name: "rating_sum", dataType: configure.dataType.INT },
1876
1922
  { name: "rating_count", dataType: configure.dataType.INT },
@@ -1931,7 +1977,15 @@ var COMMON_MEMORY_PROPERTIES = [
1931
1977
  { name: "total_significance", dataType: configure.dataType.NUMBER },
1932
1978
  // REM metadata
1933
1979
  { name: "rem_touched_at", dataType: configure.dataType.TEXT },
1934
- { name: "rem_visits", dataType: configure.dataType.INT }
1980
+ { name: "rem_visits", dataType: configure.dataType.INT },
1981
+ // User organization (M64)
1982
+ { name: "is_user_organized", dataType: configure.dataType.BOOLEAN },
1983
+ // Curation scoring (M36)
1984
+ { name: "curated_score", dataType: configure.dataType.NUMBER },
1985
+ { name: "editorial_score", dataType: configure.dataType.NUMBER },
1986
+ { name: "click_count", dataType: configure.dataType.INT },
1987
+ { name: "share_count", dataType: configure.dataType.INT },
1988
+ { name: "comment_count", dataType: configure.dataType.INT }
1935
1989
  ];
1936
1990
  var PUBLISHED_MEMORY_PROPERTIES = [
1937
1991
  // Publication metadata
@@ -2286,7 +2340,7 @@ var PreferencesDatabaseService = class {
2286
2340
  };
2287
2341
 
2288
2342
  // node_modules/@prmichaelsen/remember-core/dist/services/confirmation-token.service.js
2289
- var randomUUID = () => globalThis.crypto.randomUUID();
2343
+ var randomUUID2 = () => globalThis.crypto.randomUUID();
2290
2344
  var ConfirmationTokenService = class {
2291
2345
  EXPIRY_MINUTES = 5;
2292
2346
  logger;
@@ -2298,7 +2352,7 @@ var ConfirmationTokenService = class {
2298
2352
  */
2299
2353
  async createRequest(userId, action, payload, targetCollection) {
2300
2354
  try {
2301
- const token = randomUUID();
2355
+ const token = randomUUID2();
2302
2356
  const now = /* @__PURE__ */ new Date();
2303
2357
  const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
2304
2358
  const request = {
@@ -2582,7 +2636,13 @@ var ALL_MEMORY_PROPERTIES = [
2582
2636
  "functional_significance",
2583
2637
  "total_significance",
2584
2638
  "rem_touched_at",
2585
- "rem_visits"
2639
+ "rem_visits",
2640
+ // Curation scoring (M36)
2641
+ "curated_score",
2642
+ "editorial_score",
2643
+ "click_count",
2644
+ "share_count",
2645
+ "comment_count"
2586
2646
  ];
2587
2647
  async function fetchMemoryWithAllProperties(collection, memoryId) {
2588
2648
  try {
@@ -2823,6 +2883,7 @@ var MemoryService = class {
2823
2883
  thread_root_id: input.thread_root_id ?? null,
2824
2884
  moderation_flags: input.moderation_flags ?? [],
2825
2885
  follow_up_at: input.follow_up_at || null,
2886
+ is_user_organized: input.is_user_organized ?? false,
2826
2887
  space_ids: [],
2827
2888
  group_ids: []
2828
2889
  };
@@ -3358,6 +3419,99 @@ var MemoryService = class {
3358
3419
  total_pool_size: totalPoolSize
3359
3420
  };
3360
3421
  }
3422
+ // ── By Curated (composite quality score) ──────────────────────────
3423
+ async byCurated(input) {
3424
+ const limit = input.limit ?? 50;
3425
+ const offset = input.offset ?? 0;
3426
+ const direction = input.direction ?? "desc";
3427
+ const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
3428
+ const ghostFilters = [];
3429
+ if (input.ghost_context) {
3430
+ ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
3431
+ }
3432
+ if (!input.ghost_context?.include_ghost_content && !input.filters?.types?.includes("ghost")) {
3433
+ ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
3434
+ }
3435
+ if (!input.filters?.types?.includes("rem")) {
3436
+ ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("rem"));
3437
+ }
3438
+ const hasQuery = input.query?.trim();
3439
+ const fetchLimit = (limit + offset) * 2;
3440
+ const executeScoredQuery = async (useDeletedFilter) => {
3441
+ const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
3442
+ const scoredFilter = this.collection.filter.byProperty("curated_score").greaterThan(0);
3443
+ const combined = combineFiltersWithAnd([deletedFilter, memoryFilters, scoredFilter, ...ghostFilters].filter((f) => f !== null));
3444
+ if (hasQuery) {
3445
+ const queryOptions2 = {
3446
+ limit: fetchLimit,
3447
+ alpha: 0.7,
3448
+ query: hasQuery
3449
+ };
3450
+ if (combined)
3451
+ queryOptions2.filters = combined;
3452
+ return this.collection.query.hybrid(hasQuery, queryOptions2);
3453
+ }
3454
+ const queryOptions = {
3455
+ limit: fetchLimit,
3456
+ sort: this.collection.sort.byProperty("curated_score", direction === "asc")
3457
+ };
3458
+ if (combined)
3459
+ queryOptions.filters = combined;
3460
+ return this.collection.query.fetchObjects(queryOptions);
3461
+ };
3462
+ const executeUnscoredQuery = async (useDeletedFilter) => {
3463
+ const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
3464
+ const unscoredFilters = [];
3465
+ const combined = combineFiltersWithAnd([deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null));
3466
+ const queryOptions = {
3467
+ limit: Math.ceil(fetchLimit / 4),
3468
+ sort: this.collection.sort.byProperty("created_at", false)
3469
+ // newest first
3470
+ };
3471
+ if (combined)
3472
+ queryOptions.filters = combined;
3473
+ return this.collection.query.fetchObjects(queryOptions);
3474
+ };
3475
+ const scoredResults = await this.retryWithoutDeletedFilter(executeScoredQuery);
3476
+ const unscoredResults = await this.retryWithoutDeletedFilter(executeUnscoredQuery);
3477
+ const scored = [];
3478
+ for (const obj of scoredResults.objects) {
3479
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
3480
+ if (doc.doc_type === "memory" && doc.curated_score > 0) {
3481
+ scored.push(doc);
3482
+ }
3483
+ }
3484
+ if (hasQuery) {
3485
+ scored.sort((a, b) => {
3486
+ const aScore = a.curated_score ?? 0;
3487
+ const bScore = b.curated_score ?? 0;
3488
+ return direction === "asc" ? aScore - bScore : bScore - aScore;
3489
+ });
3490
+ }
3491
+ const unscored = [];
3492
+ for (const obj of unscoredResults.objects) {
3493
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
3494
+ if (doc.doc_type === "memory" && !doc.curated_score) {
3495
+ unscored.push(doc);
3496
+ }
3497
+ }
3498
+ const interleaved = interleaveDiscovery({
3499
+ rated: scored,
3500
+ discovery: unscored,
3501
+ offset,
3502
+ limit
3503
+ });
3504
+ const memories = interleaved.map((item) => ({
3505
+ ...item.item,
3506
+ ...item.is_discovery ? { is_discovery: true } : {}
3507
+ }));
3508
+ return {
3509
+ memories,
3510
+ total: memories.length,
3511
+ offset,
3512
+ limit
3513
+ };
3514
+ }
3361
3515
  // ── Find Similar (vector) ──────────────────────────────────────────
3362
3516
  async findSimilar(input) {
3363
3517
  if (!input.memory_id && !input.text)
@@ -3374,6 +3528,7 @@ var MemoryService = class {
3374
3528
  ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
3375
3529
  }
3376
3530
  ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("rem"));
3531
+ ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("comment"));
3377
3532
  let memoryObj = null;
3378
3533
  if (input.memory_id) {
3379
3534
  memoryObj = await this.collection.query.fetchObjectById(input.memory_id, {
@@ -3507,6 +3662,10 @@ var MemoryService = class {
3507
3662
  updates.moderation_flags = input.moderation_flags;
3508
3663
  updatedFields.push("moderation_flags");
3509
3664
  }
3665
+ if (input.is_user_organized !== void 0) {
3666
+ updates.is_user_organized = input.is_user_organized;
3667
+ updatedFields.push("is_user_organized");
3668
+ }
3510
3669
  if (updatedFields.length === 0)
3511
3670
  throw new Error("No fields provided for update");
3512
3671
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3565,6 +3724,28 @@ var MemoryService = class {
3565
3724
  orphaned_relationship_ids: orphanedIds
3566
3725
  };
3567
3726
  }
3727
+ // ── Engagement Counters ───────────────────────────────────────────
3728
+ async incrementClick(memoryId) {
3729
+ await this.incrementCounter(memoryId, "click_count");
3730
+ }
3731
+ async incrementShare(memoryId) {
3732
+ await this.incrementCounter(memoryId, "share_count");
3733
+ }
3734
+ async incrementComment(memoryId) {
3735
+ await this.incrementCounter(memoryId, "comment_count");
3736
+ }
3737
+ async incrementCounter(memoryId, field) {
3738
+ const existing = await this.collection.query.fetchObjectById(memoryId, {
3739
+ returnProperties: [field]
3740
+ });
3741
+ if (!existing)
3742
+ throw new Error(`Memory not found: ${memoryId}`);
3743
+ const current = existing.properties?.[field] ?? 0;
3744
+ await this.collection.data.update({
3745
+ id: memoryId,
3746
+ properties: { [field]: current + 1 }
3747
+ });
3748
+ }
3568
3749
  };
3569
3750
 
3570
3751
  // node_modules/@prmichaelsen/remember-core/dist/services/relationship.service.js
@@ -3617,6 +3798,7 @@ var RelationshipService = class {
3617
3798
  "confidence",
3618
3799
  "source",
3619
3800
  "tags",
3801
+ "member_count",
3620
3802
  "created_at",
3621
3803
  "updated_at",
3622
3804
  "version"
@@ -3663,6 +3845,7 @@ var RelationshipService = class {
3663
3845
  strength: input.strength ?? 0.5,
3664
3846
  confidence: input.confidence ?? 0.8,
3665
3847
  source: input.source ?? "user",
3848
+ member_count: input.memory_ids.length,
3666
3849
  created_at: now,
3667
3850
  updated_at: now,
3668
3851
  version: 1,
@@ -3766,6 +3949,9 @@ var RelationshipService = class {
3766
3949
  const opts = { alpha: 1, limit: limit + offset };
3767
3950
  if (combinedFilters)
3768
3951
  opts.filters = combinedFilters;
3952
+ if (input.sort_by) {
3953
+ opts.sort = this.collection.sort.byProperty(input.sort_by, input.sort_direction === "asc");
3954
+ }
3769
3955
  const results = await this.collection.query.hybrid(input.query, opts);
3770
3956
  const paginated = results.objects.slice(offset, offset + limit);
3771
3957
  const relationships = paginated.map((obj) => ({
@@ -3802,6 +3988,7 @@ var RelationshipService = class {
3802
3988
  "confidence",
3803
3989
  "source",
3804
3990
  "tags",
3991
+ "member_count",
3805
3992
  "created_at",
3806
3993
  "updated_at",
3807
3994
  "version"
@@ -3895,6 +4082,7 @@ var SpaceService = class {
3895
4082
  moderationClient;
3896
4083
  memoryIndex;
3897
4084
  recommendationService;
4085
+ eventBus;
3898
4086
  constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, memoryIndexService2, options) {
3899
4087
  this.weaviateClient = weaviateClient;
3900
4088
  this.userCollection = userCollection;
@@ -3904,6 +4092,7 @@ var SpaceService = class {
3904
4092
  this.memoryIndexService = memoryIndexService2;
3905
4093
  this.moderationClient = options?.moderationClient;
3906
4094
  this.recommendationService = options?.recommendationService;
4095
+ this.eventBus = options?.eventBus;
3907
4096
  this.memoryIndex = memoryIndexService2;
3908
4097
  }
3909
4098
  // ── Content moderation helper ────────────────────────────────────────
@@ -3918,6 +4107,46 @@ var SpaceService = class {
3918
4107
  });
3919
4108
  }
3920
4109
  }
4110
+ // ── Resolve composite UUID to original memory ──────────────────────
4111
+ /**
4112
+ * Looks up a memory in the user's collection. If not found, checks whether
4113
+ * the ID is a composite UUID from a published copy and resolves to the
4114
+ * original memory via composite_id or original_memory_id.
4115
+ */
4116
+ async resolveToOriginalMemory(memoryId) {
4117
+ let memory = await fetchMemoryWithAllProperties(this.userCollection, memoryId);
4118
+ if (memory) {
4119
+ if (memory.properties.user_id !== this.userId)
4120
+ throw new ForbiddenError("Permission denied: not memory owner");
4121
+ return { resolvedId: memoryId, memory };
4122
+ }
4123
+ const collectionName = await this.memoryIndex.lookup(memoryId);
4124
+ if (collectionName && collectionName !== this.userCollection.name) {
4125
+ const publishedCollection = this.weaviateClient.collections.get(collectionName);
4126
+ const published = await fetchMemoryWithAllProperties(publishedCollection, memoryId);
4127
+ if (published) {
4128
+ let originalId;
4129
+ if (published.properties.original_memory_id) {
4130
+ originalId = published.properties.original_memory_id;
4131
+ } else if (published.properties.composite_id) {
4132
+ try {
4133
+ const parsed = parseCompositeId(published.properties.composite_id);
4134
+ originalId = parsed.memoryId;
4135
+ } catch {
4136
+ }
4137
+ }
4138
+ if (originalId) {
4139
+ memory = await fetchMemoryWithAllProperties(this.userCollection, originalId);
4140
+ if (memory) {
4141
+ if (memory.properties.user_id !== this.userId)
4142
+ throw new ForbiddenError("Permission denied: not memory owner");
4143
+ return { resolvedId: originalId, memory };
4144
+ }
4145
+ }
4146
+ }
4147
+ }
4148
+ throw new NotFoundError("Memory", memoryId);
4149
+ }
3921
4150
  // ── Publish (phase 1: generate confirmation token) ──────────────────
3922
4151
  async publish(input) {
3923
4152
  const spaces = input.spaces || [];
@@ -3937,11 +4166,7 @@ var SpaceService = class {
3937
4166
  throw new ValidationError("Group IDs cannot be empty or contain dots");
3938
4167
  }
3939
4168
  }
3940
- const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
3941
- if (!memory)
3942
- throw new NotFoundError("Memory", input.memory_id);
3943
- if (memory.properties.user_id !== this.userId)
3944
- throw new ForbiddenError("Permission denied: not memory owner");
4169
+ const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
3945
4170
  if (memory.properties.doc_type !== "memory")
3946
4171
  throw new ValidationError("Only memories can be published");
3947
4172
  const memoryContentType = memory.properties.content_type;
@@ -3953,14 +4178,14 @@ var SpaceService = class {
3953
4178
  }
3954
4179
  await this.checkModeration(memory.properties.content);
3955
4180
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
3956
- memory_id: input.memory_id,
4181
+ memory_id: resolvedMemoryId,
3957
4182
  spaces,
3958
4183
  groups,
3959
4184
  additional_tags: input.additional_tags || []
3960
4185
  });
3961
4186
  this.logger.info("Publish confirmation created", {
3962
4187
  userId: this.userId,
3963
- memoryId: input.memory_id,
4188
+ memoryId: resolvedMemoryId,
3964
4189
  spaces,
3965
4190
  groups
3966
4191
  });
@@ -3979,11 +4204,7 @@ var SpaceService = class {
3979
4204
  throw new ValidationError(`Group IDs cannot contain dots: ${invalidGroups.join(", ")}`);
3980
4205
  }
3981
4206
  }
3982
- const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
3983
- if (!memory)
3984
- throw new NotFoundError("Memory", input.memory_id);
3985
- if (memory.properties.user_id !== this.userId)
3986
- throw new ForbiddenError("Permission denied: not memory owner");
4207
+ const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
3987
4208
  const currentSpaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
3988
4209
  const currentGroupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
3989
4210
  const notPublishedSpaces = spaces.filter((s) => !currentSpaceIds.includes(s));
@@ -3992,7 +4213,7 @@ var SpaceService = class {
3992
4213
  throw new ValidationError(`Memory is not published to some destinations. Not in spaces: [${notPublishedSpaces.join(", ")}], Not in groups: [${notPublishedGroups.join(", ")}]`);
3993
4214
  }
3994
4215
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "retract_memory", {
3995
- memory_id: input.memory_id,
4216
+ memory_id: resolvedMemoryId,
3996
4217
  spaces,
3997
4218
  groups,
3998
4219
  current_space_ids: currentSpaceIds,
@@ -4000,7 +4221,7 @@ var SpaceService = class {
4000
4221
  });
4001
4222
  this.logger.info("Retract confirmation created", {
4002
4223
  userId: this.userId,
4003
- memoryId: input.memory_id,
4224
+ memoryId: resolvedMemoryId,
4004
4225
  spaces,
4005
4226
  groups
4006
4227
  });
@@ -4254,6 +4475,22 @@ var SpaceService = class {
4254
4475
  total: memories.length
4255
4476
  };
4256
4477
  }
4478
+ // ── Private: Dedupe Check ──────────────────────────────────────────
4479
+ /**
4480
+ * Check that the given original_memory_id is not already published to the
4481
+ * target collection by a different user. If the same user re-publishes
4482
+ * (same weaviateId) this is fine — the caller handles update vs insert.
4483
+ */
4484
+ async checkOriginalMemoryNotPublished(collection, originalMemoryId, expectedWeaviateId) {
4485
+ const filter = collection.filter.byProperty("original_memory_id").equal(originalMemoryId);
4486
+ const result = await collection.query.fetchObjects({ filters: filter, limit: 1 });
4487
+ if (result.objects.length > 0) {
4488
+ const existing = result.objects[0];
4489
+ if (existing.uuid === expectedWeaviateId)
4490
+ return;
4491
+ throw new ValidationError(`This memory is already published by another user`);
4492
+ }
4493
+ }
4257
4494
  // ── Private: Execute Publish ────────────────────────────────────────
4258
4495
  async executePublish(request) {
4259
4496
  const spaces = request.payload.spaces || [];
@@ -4278,6 +4515,7 @@ var SpaceService = class {
4278
4515
  if (spaces.length > 0) {
4279
4516
  try {
4280
4517
  const publicCollection = await ensurePublicCollection(this.weaviateClient);
4518
+ await this.checkOriginalMemoryNotPublished(publicCollection, request.payload.memory_id, weaviateId);
4281
4519
  let existingSpaceMemory = null;
4282
4520
  try {
4283
4521
  existingSpaceMemory = await fetchMemoryWithAllProperties(publicCollection, weaviateId);
@@ -4327,6 +4565,7 @@ var SpaceService = class {
4327
4565
  try {
4328
4566
  await ensureGroupCollection(this.weaviateClient, groupId);
4329
4567
  const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
4568
+ await this.checkOriginalMemoryNotPublished(groupCollection, request.payload.memory_id, weaviateId);
4330
4569
  let existingGroupMemory = null;
4331
4570
  try {
4332
4571
  existingGroupMemory = await fetchMemoryWithAllProperties(groupCollection, weaviateId);
@@ -4385,6 +4624,35 @@ var SpaceService = class {
4385
4624
  published: successfulPublications,
4386
4625
  failed: failedPublications
4387
4626
  });
4627
+ if (this.eventBus) {
4628
+ const title = String(originalMemory.properties.title ?? "");
4629
+ const actor = { type: "user", id: this.userId };
4630
+ const isComment = originalMemory.properties.content_type === "comment";
4631
+ if (isComment) {
4632
+ const parentId = String(originalMemory.properties.parent_id ?? "");
4633
+ const threadRootId = String(originalMemory.properties.thread_root_id ?? parentId);
4634
+ const contentPreview = String(originalMemory.properties.content ?? "").slice(0, 200);
4635
+ if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4636
+ for (const spaceId of spaces) {
4637
+ this.eventBus.emit({ type: "comment.published_to_space", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, space_id: spaceId, owner_id: this.userId }, actor);
4638
+ }
4639
+ }
4640
+ const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4641
+ for (const groupId of publishedGroups) {
4642
+ this.eventBus.emit({ type: "comment.published_to_group", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, group_id: groupId, owner_id: this.userId }, actor);
4643
+ }
4644
+ } else {
4645
+ if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4646
+ for (const spaceId of spaces) {
4647
+ this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
4648
+ }
4649
+ }
4650
+ const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4651
+ for (const groupId of publishedGroups) {
4652
+ this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
4653
+ }
4654
+ }
4655
+ }
4388
4656
  return {
4389
4657
  action: "publish_memory",
4390
4658
  success: true,
@@ -4466,6 +4734,21 @@ var SpaceService = class {
4466
4734
  retracted: successfulRetractions,
4467
4735
  failed: failedRetractions
4468
4736
  });
4737
+ if (this.eventBus && successfulRetractions.length > 0) {
4738
+ const targets = [];
4739
+ if (successfulRetractions.some((r) => r.startsWith("spaces:"))) {
4740
+ for (const spaceId of spaces) {
4741
+ targets.push({ kind: "space", id: spaceId });
4742
+ }
4743
+ }
4744
+ const retractedGroups = groups.filter((g) => successfulRetractions.some((r) => r === `group: ${g}`));
4745
+ for (const groupId of retractedGroups) {
4746
+ targets.push({ kind: "group", id: groupId });
4747
+ }
4748
+ if (targets.length > 0) {
4749
+ this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
4750
+ }
4751
+ }
4469
4752
  return {
4470
4753
  action: "retract_memory",
4471
4754
  success: true,
@@ -5006,6 +5289,42 @@ var SpaceService = class {
5006
5289
  });
5007
5290
  return { spaces_searched: spacesSearched, groups_searched: groupsSearched, results, total_pool_size: totalPoolSize };
5008
5291
  }
5292
+ // ── By Curated (composite quality score) ──────────────────────────
5293
+ async byCurated(input, authContext) {
5294
+ const spaces = input.spaces || [];
5295
+ const groups = input.groups || [];
5296
+ const limit = input.limit ?? 50;
5297
+ const offset = input.offset ?? 0;
5298
+ const direction = input.direction ?? "desc";
5299
+ this.validateSpaceGroupInput(spaces, groups, input.moderation_filter || "approved", authContext);
5300
+ const fetchLimit = (limit + offset) * 2;
5301
+ const hasQuery = input.query?.trim();
5302
+ const { allResults, spacesSearched, groupsSearched } = await this.fetchAcrossCollections(input, spaces, groups, async (collection, baseFilters) => {
5303
+ const combined = baseFilters.length > 0 ? Filters4.and(...baseFilters) : void 0;
5304
+ if (hasQuery) {
5305
+ const opts2 = { limit: fetchLimit, alpha: 0.7, query: hasQuery };
5306
+ if (combined)
5307
+ opts2.filters = combined;
5308
+ return (await collection.query.hybrid(hasQuery, opts2)).objects;
5309
+ }
5310
+ const opts = {
5311
+ limit: fetchLimit,
5312
+ sort: collection.sort.byProperty("curated_score", direction === "asc")
5313
+ };
5314
+ if (combined)
5315
+ opts.filters = combined;
5316
+ return (await collection.query.fetchObjects(opts)).objects;
5317
+ });
5318
+ const deduped = dedupeBySourceId(allResults, input.dedupe);
5319
+ deduped.sort((a, b) => {
5320
+ const aVal = a.properties?.curated_score ?? 0;
5321
+ const bVal = b.properties?.curated_score ?? 0;
5322
+ return direction === "desc" ? bVal - aVal : aVal - bVal;
5323
+ });
5324
+ const paginated = deduped.slice(offset, offset + limit);
5325
+ const memories = paginated.filter((obj) => obj.properties?.doc_type === "memory").map((obj) => ({ id: obj.uuid, ...obj.properties }));
5326
+ return { spaces_searched: spacesSearched, groups_searched: groupsSearched, memories, total: memories.length, offset, limit };
5327
+ }
5009
5328
  // ── Private: Validate Space/Group Input ───────────────────────────
5010
5329
  validateSpaceGroupInput(spaces, groups, moderationFilter, authContext) {
5011
5330
  if (spaces.length > 0) {
@@ -5150,6 +5469,18 @@ var REM_STATE_COLLECTION = `${BASE}.rem_state`;
5150
5469
  // node_modules/@prmichaelsen/remember-core/dist/services/rem.clustering.js
5151
5470
  import { Filters as Filters5 } from "weaviate-client";
5152
5471
 
5472
+ // node_modules/@prmichaelsen/remember-core/dist/services/rem.service.js
5473
+ import { Filters as Filters9 } from "weaviate-client";
5474
+
5475
+ // node_modules/@prmichaelsen/remember-core/dist/services/scoring-context.service.js
5476
+ import { Filters as Filters6 } from "weaviate-client";
5477
+
5478
+ // node_modules/@prmichaelsen/remember-core/dist/services/rem.pruning.js
5479
+ import { Filters as Filters7 } from "weaviate-client";
5480
+
5481
+ // node_modules/@prmichaelsen/remember-core/dist/services/rem.reconciliation.js
5482
+ import { Filters as Filters8 } from "weaviate-client";
5483
+
5153
5484
  // node_modules/@prmichaelsen/remember-core/dist/services/classification.service.js
5154
5485
  var GENRES = [
5155
5486
  "short_story",
@@ -5347,6 +5678,132 @@ var INITIAL_PERCEPTION = {
5347
5678
  last_updated: (/* @__PURE__ */ new Date()).toISOString()
5348
5679
  };
5349
5680
 
5681
+ // node_modules/@prmichaelsen/remember-core/dist/webhooks/signing.js
5682
+ import { createHmac } from "node:crypto";
5683
+ function signWebhookPayload(webhookId, timestamp, body, secret) {
5684
+ const content = `${webhookId}.${timestamp}.${body}`;
5685
+ const hmac = createHmac("sha256", secret).update(content).digest("base64");
5686
+ return `v1,${hmac}`;
5687
+ }
5688
+
5689
+ // node_modules/@prmichaelsen/remember-core/dist/webhooks/batched-webhook.service.js
5690
+ var DEFAULT_MAX_BATCH_SIZE = 20;
5691
+ var DEFAULT_FLUSH_INTERVAL_MS = 1e3;
5692
+ var DEFAULT_TIMEOUT_MS = 5e3;
5693
+ var SOURCE = "remember-core";
5694
+ var API_VERSION = "1";
5695
+ var BatchedWebhookService = class {
5696
+ logger;
5697
+ resolveEndpoint;
5698
+ maxBatchSize;
5699
+ flushIntervalMs;
5700
+ timeoutMs;
5701
+ onError;
5702
+ buffers = /* @__PURE__ */ new Map();
5703
+ constructor(logger2, config2) {
5704
+ this.logger = logger2;
5705
+ this.resolveEndpoint = config2.resolveEndpoint;
5706
+ this.maxBatchSize = config2.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
5707
+ this.flushIntervalMs = config2.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
5708
+ this.timeoutMs = config2.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5709
+ this.onError = config2.onError;
5710
+ }
5711
+ emit(event, actor) {
5712
+ const ownerId = event.owner_id;
5713
+ const endpoints = this.resolveEndpoint(ownerId);
5714
+ if (endpoints.length === 0) {
5715
+ this.logger.debug?.("[BatchedWebhookService] no endpoints for owner, dropping event", {
5716
+ owner_id: ownerId,
5717
+ type: event.type
5718
+ });
5719
+ return;
5720
+ }
5721
+ const envelope = this.buildEnvelope(event, actor);
5722
+ for (const endpoint of endpoints) {
5723
+ const url = endpoint.url;
5724
+ let buffer = this.buffers.get(url);
5725
+ if (!buffer) {
5726
+ buffer = { endpoint, envelopes: [], timer: null };
5727
+ this.buffers.set(url, buffer);
5728
+ }
5729
+ buffer.envelopes.push(envelope);
5730
+ if (buffer.envelopes.length >= this.maxBatchSize) {
5731
+ this.flush(url);
5732
+ } else if (!buffer.timer) {
5733
+ buffer.timer = setTimeout(() => this.flush(url), this.flushIntervalMs);
5734
+ }
5735
+ }
5736
+ }
5737
+ flush(url) {
5738
+ const buffer = this.buffers.get(url);
5739
+ if (!buffer || buffer.envelopes.length === 0)
5740
+ return;
5741
+ const envelopes = buffer.envelopes;
5742
+ const endpoint = buffer.endpoint;
5743
+ if (buffer.timer) {
5744
+ clearTimeout(buffer.timer);
5745
+ }
5746
+ buffer.envelopes = [];
5747
+ buffer.timer = null;
5748
+ this.sendBatch(url, endpoint, envelopes).catch((err2) => {
5749
+ this.logger.error?.("[BatchedWebhookService] batch delivery failed", {
5750
+ error: err2,
5751
+ url,
5752
+ count: envelopes.length
5753
+ });
5754
+ this.onError?.(err2, envelopes);
5755
+ });
5756
+ }
5757
+ dispose() {
5758
+ for (const url of this.buffers.keys()) {
5759
+ this.flush(url);
5760
+ }
5761
+ }
5762
+ async sendBatch(url, endpoint, envelopes) {
5763
+ const batchId = v4_default();
5764
+ const batchTimestamp = Math.floor(Date.now() / 1e3);
5765
+ const body = JSON.stringify(envelopes);
5766
+ const signature = signWebhookPayload(batchId, batchTimestamp, body, endpoint.signingSecret);
5767
+ const controller = new AbortController();
5768
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
5769
+ try {
5770
+ const response = await fetch(url, {
5771
+ method: "POST",
5772
+ headers: {
5773
+ "Content-Type": "application/json",
5774
+ "webhook-id": batchId,
5775
+ "webhook-timestamp": String(batchTimestamp),
5776
+ "webhook-signature": signature,
5777
+ "x-webhook-batch": "true"
5778
+ },
5779
+ body,
5780
+ signal: controller.signal
5781
+ });
5782
+ if (!response.ok) {
5783
+ throw new Error(`Webhook batch delivery failed: HTTP ${response.status}`);
5784
+ }
5785
+ } finally {
5786
+ clearTimeout(timeout);
5787
+ }
5788
+ }
5789
+ buildEnvelope(event, actor) {
5790
+ return {
5791
+ id: v4_default(),
5792
+ timestamp: Math.floor(Date.now() / 1e3),
5793
+ source: SOURCE,
5794
+ api_version: API_VERSION,
5795
+ type: event.type,
5796
+ actor,
5797
+ data: event
5798
+ };
5799
+ }
5800
+ };
5801
+
5802
+ // node_modules/@prmichaelsen/remember-core/dist/webhooks/create.js
5803
+ function createBatchedWebhookService(logger2, config2) {
5804
+ return new BatchedWebhookService(logger2, config2);
5805
+ }
5806
+
5350
5807
  // src/weaviate/schema.ts
5351
5808
  init_logger();
5352
5809
 
@@ -5465,6 +5922,15 @@ var tokenService = new ConfirmationTokenService(coreLogger);
5465
5922
  var preferencesService = new PreferencesDatabaseService(coreLogger);
5466
5923
  var moderationClient = process.env.ANTHROPIC_API_KEY ? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY }) : void 0;
5467
5924
  var memoryIndexService = new MemoryIndexService(coreLogger);
5925
+ var eventBus = (() => {
5926
+ const url = process.env.REMEMBER_WEBHOOK_URL;
5927
+ const secret = process.env.REMEMBER_WEBHOOK_SECRET;
5928
+ if (!url || !secret)
5929
+ return void 0;
5930
+ return createBatchedWebhookService(coreLogger, {
5931
+ resolveEndpoint: () => [{ url, signingSecret: secret }]
5932
+ });
5933
+ })();
5468
5934
  var coreServicesCache = /* @__PURE__ */ new Map();
5469
5935
  function createCoreServices(userId) {
5470
5936
  const cached = coreServicesCache.get(userId);
@@ -5478,7 +5944,7 @@ function createCoreServices(userId) {
5478
5944
  weaviateClient
5479
5945
  }),
5480
5946
  relationship: new RelationshipService(collection, userId, coreLogger),
5481
- space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient }),
5947
+ space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient, eventBus }),
5482
5948
  preferences: preferencesService,
5483
5949
  token: tokenService
5484
5950
  };
@@ -7437,7 +7903,7 @@ async function handleDeny(args, userId, authContext) {
7437
7903
  }
7438
7904
 
7439
7905
  // src/tools/search-space.ts
7440
- import { Filters as Filters6 } from "weaviate-client";
7906
+ import { Filters as Filters10 } from "weaviate-client";
7441
7907
  var searchSpaceTool = {
7442
7908
  name: "remember_search_space",
7443
7909
  description: `Search shared spaces and/or groups to discover memories from other users.