@prmichaelsen/remember-mcp 3.16.2 → 3.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/server-factory.e2e.d.ts +2 -0
- package/dist/server-factory.js +307 -17
- package/dist/server.js +307 -17
- package/jest.config.js +1 -0
- package/package.json +2 -2
- package/src/core-services.ts +13 -2
- package/dist/server-factory.spec.d.ts +0 -2
- /package/src/{server-factory.spec.ts → server-factory.e2e.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.17.1] - 2026-03-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Move `server-factory.spec.ts` to `server-factory.e2e.ts` — test requires live Weaviate and was crashing CI workers
|
|
13
|
+
- Revert CI test command to `npm test` (OOM was a symptom of the Weaviate connection failure, not memory)
|
|
14
|
+
|
|
15
|
+
## [3.17.0] - 2026-03-09
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Webhook event bus integration — `SpaceService` now emits `memory.published_to_group`, `memory.published_to_space`, `comment.published_to_group`, and `comment.published_to_space` events via `BatchedWebhookService`
|
|
20
|
+
- `REMEMBER_WEBHOOK_URL` and `REMEMBER_WEBHOOK_SECRET` env vars configure outbound webhook delivery
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Bump `@prmichaelsen/remember-core` to 0.54.0 (fan-out webhooks, comment events, publish dedupe)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- CI OOM crash in `server-factory.spec.ts` — add `maxWorkers: '50%'` to jest config
|
|
29
|
+
|
|
8
30
|
## [3.16.0] - 2026-03-08
|
|
9
31
|
|
|
10
32
|
### Added
|
package/dist/server-factory.js
CHANGED
|
@@ -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 },
|
|
@@ -1932,6 +1978,8 @@ var COMMON_MEMORY_PROPERTIES = [
|
|
|
1932
1978
|
// REM metadata
|
|
1933
1979
|
{ name: "rem_touched_at", dataType: configure.dataType.TEXT },
|
|
1934
1980
|
{ name: "rem_visits", dataType: configure.dataType.INT },
|
|
1981
|
+
// User organization (M64)
|
|
1982
|
+
{ name: "is_user_organized", dataType: configure.dataType.BOOLEAN },
|
|
1935
1983
|
// Curation scoring (M36)
|
|
1936
1984
|
{ name: "curated_score", dataType: configure.dataType.NUMBER },
|
|
1937
1985
|
{ name: "editorial_score", dataType: configure.dataType.NUMBER },
|
|
@@ -2292,7 +2340,7 @@ var PreferencesDatabaseService = class {
|
|
|
2292
2340
|
};
|
|
2293
2341
|
|
|
2294
2342
|
// node_modules/@prmichaelsen/remember-core/dist/services/confirmation-token.service.js
|
|
2295
|
-
var
|
|
2343
|
+
var randomUUID2 = () => globalThis.crypto.randomUUID();
|
|
2296
2344
|
var ConfirmationTokenService = class {
|
|
2297
2345
|
EXPIRY_MINUTES = 5;
|
|
2298
2346
|
logger;
|
|
@@ -2304,7 +2352,7 @@ var ConfirmationTokenService = class {
|
|
|
2304
2352
|
*/
|
|
2305
2353
|
async createRequest(userId, action, payload, targetCollection) {
|
|
2306
2354
|
try {
|
|
2307
|
-
const token =
|
|
2355
|
+
const token = randomUUID2();
|
|
2308
2356
|
const now = /* @__PURE__ */ new Date();
|
|
2309
2357
|
const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
|
|
2310
2358
|
const request = {
|
|
@@ -2835,6 +2883,7 @@ var MemoryService = class {
|
|
|
2835
2883
|
thread_root_id: input.thread_root_id ?? null,
|
|
2836
2884
|
moderation_flags: input.moderation_flags ?? [],
|
|
2837
2885
|
follow_up_at: input.follow_up_at || null,
|
|
2886
|
+
is_user_organized: input.is_user_organized ?? false,
|
|
2838
2887
|
space_ids: [],
|
|
2839
2888
|
group_ids: []
|
|
2840
2889
|
};
|
|
@@ -3613,6 +3662,10 @@ var MemoryService = class {
|
|
|
3613
3662
|
updates.moderation_flags = input.moderation_flags;
|
|
3614
3663
|
updatedFields.push("moderation_flags");
|
|
3615
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
|
+
}
|
|
3616
3669
|
if (updatedFields.length === 0)
|
|
3617
3670
|
throw new Error("No fields provided for update");
|
|
3618
3671
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3745,6 +3798,7 @@ var RelationshipService = class {
|
|
|
3745
3798
|
"confidence",
|
|
3746
3799
|
"source",
|
|
3747
3800
|
"tags",
|
|
3801
|
+
"member_count",
|
|
3748
3802
|
"created_at",
|
|
3749
3803
|
"updated_at",
|
|
3750
3804
|
"version"
|
|
@@ -3791,6 +3845,7 @@ var RelationshipService = class {
|
|
|
3791
3845
|
strength: input.strength ?? 0.5,
|
|
3792
3846
|
confidence: input.confidence ?? 0.8,
|
|
3793
3847
|
source: input.source ?? "user",
|
|
3848
|
+
member_count: input.memory_ids.length,
|
|
3794
3849
|
created_at: now,
|
|
3795
3850
|
updated_at: now,
|
|
3796
3851
|
version: 1,
|
|
@@ -3894,6 +3949,9 @@ var RelationshipService = class {
|
|
|
3894
3949
|
const opts = { alpha: 1, limit: limit + offset };
|
|
3895
3950
|
if (combinedFilters)
|
|
3896
3951
|
opts.filters = combinedFilters;
|
|
3952
|
+
if (input.sort_by) {
|
|
3953
|
+
opts.sort = this.collection.sort.byProperty(input.sort_by, input.sort_direction === "asc");
|
|
3954
|
+
}
|
|
3897
3955
|
const results = await this.collection.query.hybrid(input.query, opts);
|
|
3898
3956
|
const paginated = results.objects.slice(offset, offset + limit);
|
|
3899
3957
|
const relationships = paginated.map((obj) => ({
|
|
@@ -3930,6 +3988,7 @@ var RelationshipService = class {
|
|
|
3930
3988
|
"confidence",
|
|
3931
3989
|
"source",
|
|
3932
3990
|
"tags",
|
|
3991
|
+
"member_count",
|
|
3933
3992
|
"created_at",
|
|
3934
3993
|
"updated_at",
|
|
3935
3994
|
"version"
|
|
@@ -4023,6 +4082,7 @@ var SpaceService = class {
|
|
|
4023
4082
|
moderationClient;
|
|
4024
4083
|
memoryIndex;
|
|
4025
4084
|
recommendationService;
|
|
4085
|
+
eventBus;
|
|
4026
4086
|
constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, memoryIndexService2, options) {
|
|
4027
4087
|
this.weaviateClient = weaviateClient;
|
|
4028
4088
|
this.userCollection = userCollection;
|
|
@@ -4032,6 +4092,7 @@ var SpaceService = class {
|
|
|
4032
4092
|
this.memoryIndexService = memoryIndexService2;
|
|
4033
4093
|
this.moderationClient = options?.moderationClient;
|
|
4034
4094
|
this.recommendationService = options?.recommendationService;
|
|
4095
|
+
this.eventBus = options?.eventBus;
|
|
4035
4096
|
this.memoryIndex = memoryIndexService2;
|
|
4036
4097
|
}
|
|
4037
4098
|
// ── Content moderation helper ────────────────────────────────────────
|
|
@@ -4046,6 +4107,46 @@ var SpaceService = class {
|
|
|
4046
4107
|
});
|
|
4047
4108
|
}
|
|
4048
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
|
+
}
|
|
4049
4150
|
// ── Publish (phase 1: generate confirmation token) ──────────────────
|
|
4050
4151
|
async publish(input) {
|
|
4051
4152
|
const spaces = input.spaces || [];
|
|
@@ -4065,11 +4166,7 @@ var SpaceService = class {
|
|
|
4065
4166
|
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
4066
4167
|
}
|
|
4067
4168
|
}
|
|
4068
|
-
const memory = await
|
|
4069
|
-
if (!memory)
|
|
4070
|
-
throw new NotFoundError("Memory", input.memory_id);
|
|
4071
|
-
if (memory.properties.user_id !== this.userId)
|
|
4072
|
-
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4169
|
+
const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
|
|
4073
4170
|
if (memory.properties.doc_type !== "memory")
|
|
4074
4171
|
throw new ValidationError("Only memories can be published");
|
|
4075
4172
|
const memoryContentType = memory.properties.content_type;
|
|
@@ -4081,14 +4178,14 @@ var SpaceService = class {
|
|
|
4081
4178
|
}
|
|
4082
4179
|
await this.checkModeration(memory.properties.content);
|
|
4083
4180
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
|
|
4084
|
-
memory_id:
|
|
4181
|
+
memory_id: resolvedMemoryId,
|
|
4085
4182
|
spaces,
|
|
4086
4183
|
groups,
|
|
4087
4184
|
additional_tags: input.additional_tags || []
|
|
4088
4185
|
});
|
|
4089
4186
|
this.logger.info("Publish confirmation created", {
|
|
4090
4187
|
userId: this.userId,
|
|
4091
|
-
memoryId:
|
|
4188
|
+
memoryId: resolvedMemoryId,
|
|
4092
4189
|
spaces,
|
|
4093
4190
|
groups
|
|
4094
4191
|
});
|
|
@@ -4107,11 +4204,7 @@ var SpaceService = class {
|
|
|
4107
4204
|
throw new ValidationError(`Group IDs cannot contain dots: ${invalidGroups.join(", ")}`);
|
|
4108
4205
|
}
|
|
4109
4206
|
}
|
|
4110
|
-
const memory = await
|
|
4111
|
-
if (!memory)
|
|
4112
|
-
throw new NotFoundError("Memory", input.memory_id);
|
|
4113
|
-
if (memory.properties.user_id !== this.userId)
|
|
4114
|
-
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4207
|
+
const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
|
|
4115
4208
|
const currentSpaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
4116
4209
|
const currentGroupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
4117
4210
|
const notPublishedSpaces = spaces.filter((s) => !currentSpaceIds.includes(s));
|
|
@@ -4120,7 +4213,7 @@ var SpaceService = class {
|
|
|
4120
4213
|
throw new ValidationError(`Memory is not published to some destinations. Not in spaces: [${notPublishedSpaces.join(", ")}], Not in groups: [${notPublishedGroups.join(", ")}]`);
|
|
4121
4214
|
}
|
|
4122
4215
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "retract_memory", {
|
|
4123
|
-
memory_id:
|
|
4216
|
+
memory_id: resolvedMemoryId,
|
|
4124
4217
|
spaces,
|
|
4125
4218
|
groups,
|
|
4126
4219
|
current_space_ids: currentSpaceIds,
|
|
@@ -4128,7 +4221,7 @@ var SpaceService = class {
|
|
|
4128
4221
|
});
|
|
4129
4222
|
this.logger.info("Retract confirmation created", {
|
|
4130
4223
|
userId: this.userId,
|
|
4131
|
-
memoryId:
|
|
4224
|
+
memoryId: resolvedMemoryId,
|
|
4132
4225
|
spaces,
|
|
4133
4226
|
groups
|
|
4134
4227
|
});
|
|
@@ -4382,6 +4475,22 @@ var SpaceService = class {
|
|
|
4382
4475
|
total: memories.length
|
|
4383
4476
|
};
|
|
4384
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
|
+
}
|
|
4385
4494
|
// ── Private: Execute Publish ────────────────────────────────────────
|
|
4386
4495
|
async executePublish(request) {
|
|
4387
4496
|
const spaces = request.payload.spaces || [];
|
|
@@ -4406,6 +4515,7 @@ var SpaceService = class {
|
|
|
4406
4515
|
if (spaces.length > 0) {
|
|
4407
4516
|
try {
|
|
4408
4517
|
const publicCollection = await ensurePublicCollection(this.weaviateClient);
|
|
4518
|
+
await this.checkOriginalMemoryNotPublished(publicCollection, request.payload.memory_id, weaviateId);
|
|
4409
4519
|
let existingSpaceMemory = null;
|
|
4410
4520
|
try {
|
|
4411
4521
|
existingSpaceMemory = await fetchMemoryWithAllProperties(publicCollection, weaviateId);
|
|
@@ -4455,6 +4565,7 @@ var SpaceService = class {
|
|
|
4455
4565
|
try {
|
|
4456
4566
|
await ensureGroupCollection(this.weaviateClient, groupId);
|
|
4457
4567
|
const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
|
|
4568
|
+
await this.checkOriginalMemoryNotPublished(groupCollection, request.payload.memory_id, weaviateId);
|
|
4458
4569
|
let existingGroupMemory = null;
|
|
4459
4570
|
try {
|
|
4460
4571
|
existingGroupMemory = await fetchMemoryWithAllProperties(groupCollection, weaviateId);
|
|
@@ -4513,6 +4624,35 @@ var SpaceService = class {
|
|
|
4513
4624
|
published: successfulPublications,
|
|
4514
4625
|
failed: failedPublications
|
|
4515
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
|
+
}
|
|
4516
4656
|
return {
|
|
4517
4657
|
action: "publish_memory",
|
|
4518
4658
|
success: true,
|
|
@@ -4594,6 +4734,21 @@ var SpaceService = class {
|
|
|
4594
4734
|
retracted: successfulRetractions,
|
|
4595
4735
|
failed: failedRetractions
|
|
4596
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
|
+
}
|
|
4597
4752
|
return {
|
|
4598
4753
|
action: "retract_memory",
|
|
4599
4754
|
success: true,
|
|
@@ -5523,6 +5678,132 @@ var INITIAL_PERCEPTION = {
|
|
|
5523
5678
|
last_updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5524
5679
|
};
|
|
5525
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
|
+
|
|
5526
5807
|
// src/weaviate/schema.ts
|
|
5527
5808
|
init_logger();
|
|
5528
5809
|
|
|
@@ -5641,6 +5922,15 @@ var tokenService = new ConfirmationTokenService(coreLogger);
|
|
|
5641
5922
|
var preferencesService = new PreferencesDatabaseService(coreLogger);
|
|
5642
5923
|
var moderationClient = process.env.ANTHROPIC_API_KEY ? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY }) : void 0;
|
|
5643
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
|
+
})();
|
|
5644
5934
|
var coreServicesCache = /* @__PURE__ */ new Map();
|
|
5645
5935
|
function createCoreServices(userId) {
|
|
5646
5936
|
const cached = coreServicesCache.get(userId);
|
|
@@ -5654,7 +5944,7 @@ function createCoreServices(userId) {
|
|
|
5654
5944
|
weaviateClient
|
|
5655
5945
|
}),
|
|
5656
5946
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
5657
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient }),
|
|
5947
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient, eventBus }),
|
|
5658
5948
|
preferences: preferencesService,
|
|
5659
5949
|
token: tokenService
|
|
5660
5950
|
};
|
package/dist/server.js
CHANGED
|
@@ -1462,6 +1462,18 @@ function unsafeStringify(arr, offset = 0) {
|
|
|
1462
1462
|
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();
|
|
1463
1463
|
}
|
|
1464
1464
|
|
|
1465
|
+
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/rng.js
|
|
1466
|
+
import { randomFillSync } from "crypto";
|
|
1467
|
+
var rnds8Pool = new Uint8Array(256);
|
|
1468
|
+
var poolPtr = rnds8Pool.length;
|
|
1469
|
+
function rng() {
|
|
1470
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
1471
|
+
randomFillSync(rnds8Pool);
|
|
1472
|
+
poolPtr = 0;
|
|
1473
|
+
}
|
|
1474
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1465
1477
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/v35.js
|
|
1466
1478
|
function stringToBytes(str) {
|
|
1467
1479
|
str = unescape(encodeURIComponent(str));
|
|
@@ -1498,6 +1510,36 @@ function v35(version, hash, value, namespace, buf, offset) {
|
|
|
1498
1510
|
return unsafeStringify(bytes);
|
|
1499
1511
|
}
|
|
1500
1512
|
|
|
1513
|
+
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/native.js
|
|
1514
|
+
import { randomUUID } from "crypto";
|
|
1515
|
+
var native_default = { randomUUID };
|
|
1516
|
+
|
|
1517
|
+
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/v4.js
|
|
1518
|
+
function v4(options, buf, offset) {
|
|
1519
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
1520
|
+
return native_default.randomUUID();
|
|
1521
|
+
}
|
|
1522
|
+
options = options || {};
|
|
1523
|
+
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
1524
|
+
if (rnds.length < 16) {
|
|
1525
|
+
throw new Error("Random bytes length must be >= 16");
|
|
1526
|
+
}
|
|
1527
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
1528
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
1529
|
+
if (buf) {
|
|
1530
|
+
offset = offset || 0;
|
|
1531
|
+
if (offset < 0 || offset + 16 > buf.length) {
|
|
1532
|
+
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
1533
|
+
}
|
|
1534
|
+
for (let i = 0; i < 16; ++i) {
|
|
1535
|
+
buf[offset + i] = rnds[i];
|
|
1536
|
+
}
|
|
1537
|
+
return buf;
|
|
1538
|
+
}
|
|
1539
|
+
return unsafeStringify(rnds);
|
|
1540
|
+
}
|
|
1541
|
+
var v4_default = v4;
|
|
1542
|
+
|
|
1501
1543
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/sha1.js
|
|
1502
1544
|
import { createHash } from "crypto";
|
|
1503
1545
|
function sha1(bytes) {
|
|
@@ -1716,6 +1758,9 @@ function buildDocTypeFilters(collection, docType, filters) {
|
|
|
1716
1758
|
if (filters?.tags && filters.tags.length > 0) {
|
|
1717
1759
|
filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
|
|
1718
1760
|
}
|
|
1761
|
+
if (filters?.is_user_organized !== void 0) {
|
|
1762
|
+
filterList.push(collection.filter.byProperty("is_user_organized").equal(filters.is_user_organized));
|
|
1763
|
+
}
|
|
1719
1764
|
if (filters?.memory_ids && filters.memory_ids.length > 0) {
|
|
1720
1765
|
filterList.push(collection.filter.byId().containsAny(filters.memory_ids));
|
|
1721
1766
|
}
|
|
@@ -1875,6 +1920,7 @@ var COMMON_MEMORY_PROPERTIES = [
|
|
|
1875
1920
|
{ name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
|
|
1876
1921
|
{ name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
|
|
1877
1922
|
{ name: "relationship_count", dataType: configure.dataType.INT },
|
|
1923
|
+
{ name: "member_count", dataType: configure.dataType.INT },
|
|
1878
1924
|
// Rating aggregates (denormalized from Firestore individual ratings)
|
|
1879
1925
|
{ name: "rating_sum", dataType: configure.dataType.INT },
|
|
1880
1926
|
{ name: "rating_count", dataType: configure.dataType.INT },
|
|
@@ -1936,6 +1982,8 @@ var COMMON_MEMORY_PROPERTIES = [
|
|
|
1936
1982
|
// REM metadata
|
|
1937
1983
|
{ name: "rem_touched_at", dataType: configure.dataType.TEXT },
|
|
1938
1984
|
{ name: "rem_visits", dataType: configure.dataType.INT },
|
|
1985
|
+
// User organization (M64)
|
|
1986
|
+
{ name: "is_user_organized", dataType: configure.dataType.BOOLEAN },
|
|
1939
1987
|
// Curation scoring (M36)
|
|
1940
1988
|
{ name: "curated_score", dataType: configure.dataType.NUMBER },
|
|
1941
1989
|
{ name: "editorial_score", dataType: configure.dataType.NUMBER },
|
|
@@ -2296,7 +2344,7 @@ var PreferencesDatabaseService = class {
|
|
|
2296
2344
|
};
|
|
2297
2345
|
|
|
2298
2346
|
// node_modules/@prmichaelsen/remember-core/dist/services/confirmation-token.service.js
|
|
2299
|
-
var
|
|
2347
|
+
var randomUUID2 = () => globalThis.crypto.randomUUID();
|
|
2300
2348
|
var ConfirmationTokenService = class {
|
|
2301
2349
|
EXPIRY_MINUTES = 5;
|
|
2302
2350
|
logger;
|
|
@@ -2308,7 +2356,7 @@ var ConfirmationTokenService = class {
|
|
|
2308
2356
|
*/
|
|
2309
2357
|
async createRequest(userId, action, payload, targetCollection) {
|
|
2310
2358
|
try {
|
|
2311
|
-
const token =
|
|
2359
|
+
const token = randomUUID2();
|
|
2312
2360
|
const now = /* @__PURE__ */ new Date();
|
|
2313
2361
|
const expiresAt = new Date(now.getTime() + this.EXPIRY_MINUTES * 60 * 1e3);
|
|
2314
2362
|
const request = {
|
|
@@ -2839,6 +2887,7 @@ var MemoryService = class {
|
|
|
2839
2887
|
thread_root_id: input.thread_root_id ?? null,
|
|
2840
2888
|
moderation_flags: input.moderation_flags ?? [],
|
|
2841
2889
|
follow_up_at: input.follow_up_at || null,
|
|
2890
|
+
is_user_organized: input.is_user_organized ?? false,
|
|
2842
2891
|
space_ids: [],
|
|
2843
2892
|
group_ids: []
|
|
2844
2893
|
};
|
|
@@ -3617,6 +3666,10 @@ var MemoryService = class {
|
|
|
3617
3666
|
updates.moderation_flags = input.moderation_flags;
|
|
3618
3667
|
updatedFields.push("moderation_flags");
|
|
3619
3668
|
}
|
|
3669
|
+
if (input.is_user_organized !== void 0) {
|
|
3670
|
+
updates.is_user_organized = input.is_user_organized;
|
|
3671
|
+
updatedFields.push("is_user_organized");
|
|
3672
|
+
}
|
|
3620
3673
|
if (updatedFields.length === 0)
|
|
3621
3674
|
throw new Error("No fields provided for update");
|
|
3622
3675
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3749,6 +3802,7 @@ var RelationshipService = class {
|
|
|
3749
3802
|
"confidence",
|
|
3750
3803
|
"source",
|
|
3751
3804
|
"tags",
|
|
3805
|
+
"member_count",
|
|
3752
3806
|
"created_at",
|
|
3753
3807
|
"updated_at",
|
|
3754
3808
|
"version"
|
|
@@ -3795,6 +3849,7 @@ var RelationshipService = class {
|
|
|
3795
3849
|
strength: input.strength ?? 0.5,
|
|
3796
3850
|
confidence: input.confidence ?? 0.8,
|
|
3797
3851
|
source: input.source ?? "user",
|
|
3852
|
+
member_count: input.memory_ids.length,
|
|
3798
3853
|
created_at: now,
|
|
3799
3854
|
updated_at: now,
|
|
3800
3855
|
version: 1,
|
|
@@ -3898,6 +3953,9 @@ var RelationshipService = class {
|
|
|
3898
3953
|
const opts = { alpha: 1, limit: limit + offset };
|
|
3899
3954
|
if (combinedFilters)
|
|
3900
3955
|
opts.filters = combinedFilters;
|
|
3956
|
+
if (input.sort_by) {
|
|
3957
|
+
opts.sort = this.collection.sort.byProperty(input.sort_by, input.sort_direction === "asc");
|
|
3958
|
+
}
|
|
3901
3959
|
const results = await this.collection.query.hybrid(input.query, opts);
|
|
3902
3960
|
const paginated = results.objects.slice(offset, offset + limit);
|
|
3903
3961
|
const relationships = paginated.map((obj) => ({
|
|
@@ -3934,6 +3992,7 @@ var RelationshipService = class {
|
|
|
3934
3992
|
"confidence",
|
|
3935
3993
|
"source",
|
|
3936
3994
|
"tags",
|
|
3995
|
+
"member_count",
|
|
3937
3996
|
"created_at",
|
|
3938
3997
|
"updated_at",
|
|
3939
3998
|
"version"
|
|
@@ -4027,6 +4086,7 @@ var SpaceService = class {
|
|
|
4027
4086
|
moderationClient;
|
|
4028
4087
|
memoryIndex;
|
|
4029
4088
|
recommendationService;
|
|
4089
|
+
eventBus;
|
|
4030
4090
|
constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, memoryIndexService2, options) {
|
|
4031
4091
|
this.weaviateClient = weaviateClient;
|
|
4032
4092
|
this.userCollection = userCollection;
|
|
@@ -4036,6 +4096,7 @@ var SpaceService = class {
|
|
|
4036
4096
|
this.memoryIndexService = memoryIndexService2;
|
|
4037
4097
|
this.moderationClient = options?.moderationClient;
|
|
4038
4098
|
this.recommendationService = options?.recommendationService;
|
|
4099
|
+
this.eventBus = options?.eventBus;
|
|
4039
4100
|
this.memoryIndex = memoryIndexService2;
|
|
4040
4101
|
}
|
|
4041
4102
|
// ── Content moderation helper ────────────────────────────────────────
|
|
@@ -4050,6 +4111,46 @@ var SpaceService = class {
|
|
|
4050
4111
|
});
|
|
4051
4112
|
}
|
|
4052
4113
|
}
|
|
4114
|
+
// ── Resolve composite UUID to original memory ──────────────────────
|
|
4115
|
+
/**
|
|
4116
|
+
* Looks up a memory in the user's collection. If not found, checks whether
|
|
4117
|
+
* the ID is a composite UUID from a published copy and resolves to the
|
|
4118
|
+
* original memory via composite_id or original_memory_id.
|
|
4119
|
+
*/
|
|
4120
|
+
async resolveToOriginalMemory(memoryId) {
|
|
4121
|
+
let memory = await fetchMemoryWithAllProperties(this.userCollection, memoryId);
|
|
4122
|
+
if (memory) {
|
|
4123
|
+
if (memory.properties.user_id !== this.userId)
|
|
4124
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4125
|
+
return { resolvedId: memoryId, memory };
|
|
4126
|
+
}
|
|
4127
|
+
const collectionName = await this.memoryIndex.lookup(memoryId);
|
|
4128
|
+
if (collectionName && collectionName !== this.userCollection.name) {
|
|
4129
|
+
const publishedCollection = this.weaviateClient.collections.get(collectionName);
|
|
4130
|
+
const published = await fetchMemoryWithAllProperties(publishedCollection, memoryId);
|
|
4131
|
+
if (published) {
|
|
4132
|
+
let originalId;
|
|
4133
|
+
if (published.properties.original_memory_id) {
|
|
4134
|
+
originalId = published.properties.original_memory_id;
|
|
4135
|
+
} else if (published.properties.composite_id) {
|
|
4136
|
+
try {
|
|
4137
|
+
const parsed = parseCompositeId(published.properties.composite_id);
|
|
4138
|
+
originalId = parsed.memoryId;
|
|
4139
|
+
} catch {
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
if (originalId) {
|
|
4143
|
+
memory = await fetchMemoryWithAllProperties(this.userCollection, originalId);
|
|
4144
|
+
if (memory) {
|
|
4145
|
+
if (memory.properties.user_id !== this.userId)
|
|
4146
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4147
|
+
return { resolvedId: originalId, memory };
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
throw new NotFoundError("Memory", memoryId);
|
|
4153
|
+
}
|
|
4053
4154
|
// ── Publish (phase 1: generate confirmation token) ──────────────────
|
|
4054
4155
|
async publish(input) {
|
|
4055
4156
|
const spaces = input.spaces || [];
|
|
@@ -4069,11 +4170,7 @@ var SpaceService = class {
|
|
|
4069
4170
|
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
4070
4171
|
}
|
|
4071
4172
|
}
|
|
4072
|
-
const memory = await
|
|
4073
|
-
if (!memory)
|
|
4074
|
-
throw new NotFoundError("Memory", input.memory_id);
|
|
4075
|
-
if (memory.properties.user_id !== this.userId)
|
|
4076
|
-
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4173
|
+
const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
|
|
4077
4174
|
if (memory.properties.doc_type !== "memory")
|
|
4078
4175
|
throw new ValidationError("Only memories can be published");
|
|
4079
4176
|
const memoryContentType = memory.properties.content_type;
|
|
@@ -4085,14 +4182,14 @@ var SpaceService = class {
|
|
|
4085
4182
|
}
|
|
4086
4183
|
await this.checkModeration(memory.properties.content);
|
|
4087
4184
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
|
|
4088
|
-
memory_id:
|
|
4185
|
+
memory_id: resolvedMemoryId,
|
|
4089
4186
|
spaces,
|
|
4090
4187
|
groups,
|
|
4091
4188
|
additional_tags: input.additional_tags || []
|
|
4092
4189
|
});
|
|
4093
4190
|
this.logger.info("Publish confirmation created", {
|
|
4094
4191
|
userId: this.userId,
|
|
4095
|
-
memoryId:
|
|
4192
|
+
memoryId: resolvedMemoryId,
|
|
4096
4193
|
spaces,
|
|
4097
4194
|
groups
|
|
4098
4195
|
});
|
|
@@ -4111,11 +4208,7 @@ var SpaceService = class {
|
|
|
4111
4208
|
throw new ValidationError(`Group IDs cannot contain dots: ${invalidGroups.join(", ")}`);
|
|
4112
4209
|
}
|
|
4113
4210
|
}
|
|
4114
|
-
const memory = await
|
|
4115
|
-
if (!memory)
|
|
4116
|
-
throw new NotFoundError("Memory", input.memory_id);
|
|
4117
|
-
if (memory.properties.user_id !== this.userId)
|
|
4118
|
-
throw new ForbiddenError("Permission denied: not memory owner");
|
|
4211
|
+
const { resolvedId: resolvedMemoryId, memory } = await this.resolveToOriginalMemory(input.memory_id);
|
|
4119
4212
|
const currentSpaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
4120
4213
|
const currentGroupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
4121
4214
|
const notPublishedSpaces = spaces.filter((s) => !currentSpaceIds.includes(s));
|
|
@@ -4124,7 +4217,7 @@ var SpaceService = class {
|
|
|
4124
4217
|
throw new ValidationError(`Memory is not published to some destinations. Not in spaces: [${notPublishedSpaces.join(", ")}], Not in groups: [${notPublishedGroups.join(", ")}]`);
|
|
4125
4218
|
}
|
|
4126
4219
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "retract_memory", {
|
|
4127
|
-
memory_id:
|
|
4220
|
+
memory_id: resolvedMemoryId,
|
|
4128
4221
|
spaces,
|
|
4129
4222
|
groups,
|
|
4130
4223
|
current_space_ids: currentSpaceIds,
|
|
@@ -4132,7 +4225,7 @@ var SpaceService = class {
|
|
|
4132
4225
|
});
|
|
4133
4226
|
this.logger.info("Retract confirmation created", {
|
|
4134
4227
|
userId: this.userId,
|
|
4135
|
-
memoryId:
|
|
4228
|
+
memoryId: resolvedMemoryId,
|
|
4136
4229
|
spaces,
|
|
4137
4230
|
groups
|
|
4138
4231
|
});
|
|
@@ -4386,6 +4479,22 @@ var SpaceService = class {
|
|
|
4386
4479
|
total: memories.length
|
|
4387
4480
|
};
|
|
4388
4481
|
}
|
|
4482
|
+
// ── Private: Dedupe Check ──────────────────────────────────────────
|
|
4483
|
+
/**
|
|
4484
|
+
* Check that the given original_memory_id is not already published to the
|
|
4485
|
+
* target collection by a different user. If the same user re-publishes
|
|
4486
|
+
* (same weaviateId) this is fine — the caller handles update vs insert.
|
|
4487
|
+
*/
|
|
4488
|
+
async checkOriginalMemoryNotPublished(collection, originalMemoryId, expectedWeaviateId) {
|
|
4489
|
+
const filter = collection.filter.byProperty("original_memory_id").equal(originalMemoryId);
|
|
4490
|
+
const result = await collection.query.fetchObjects({ filters: filter, limit: 1 });
|
|
4491
|
+
if (result.objects.length > 0) {
|
|
4492
|
+
const existing = result.objects[0];
|
|
4493
|
+
if (existing.uuid === expectedWeaviateId)
|
|
4494
|
+
return;
|
|
4495
|
+
throw new ValidationError(`This memory is already published by another user`);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4389
4498
|
// ── Private: Execute Publish ────────────────────────────────────────
|
|
4390
4499
|
async executePublish(request) {
|
|
4391
4500
|
const spaces = request.payload.spaces || [];
|
|
@@ -4410,6 +4519,7 @@ var SpaceService = class {
|
|
|
4410
4519
|
if (spaces.length > 0) {
|
|
4411
4520
|
try {
|
|
4412
4521
|
const publicCollection = await ensurePublicCollection(this.weaviateClient);
|
|
4522
|
+
await this.checkOriginalMemoryNotPublished(publicCollection, request.payload.memory_id, weaviateId);
|
|
4413
4523
|
let existingSpaceMemory = null;
|
|
4414
4524
|
try {
|
|
4415
4525
|
existingSpaceMemory = await fetchMemoryWithAllProperties(publicCollection, weaviateId);
|
|
@@ -4459,6 +4569,7 @@ var SpaceService = class {
|
|
|
4459
4569
|
try {
|
|
4460
4570
|
await ensureGroupCollection(this.weaviateClient, groupId);
|
|
4461
4571
|
const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
|
|
4572
|
+
await this.checkOriginalMemoryNotPublished(groupCollection, request.payload.memory_id, weaviateId);
|
|
4462
4573
|
let existingGroupMemory = null;
|
|
4463
4574
|
try {
|
|
4464
4575
|
existingGroupMemory = await fetchMemoryWithAllProperties(groupCollection, weaviateId);
|
|
@@ -4517,6 +4628,35 @@ var SpaceService = class {
|
|
|
4517
4628
|
published: successfulPublications,
|
|
4518
4629
|
failed: failedPublications
|
|
4519
4630
|
});
|
|
4631
|
+
if (this.eventBus) {
|
|
4632
|
+
const title = String(originalMemory.properties.title ?? "");
|
|
4633
|
+
const actor = { type: "user", id: this.userId };
|
|
4634
|
+
const isComment = originalMemory.properties.content_type === "comment";
|
|
4635
|
+
if (isComment) {
|
|
4636
|
+
const parentId = String(originalMemory.properties.parent_id ?? "");
|
|
4637
|
+
const threadRootId = String(originalMemory.properties.thread_root_id ?? parentId);
|
|
4638
|
+
const contentPreview = String(originalMemory.properties.content ?? "").slice(0, 200);
|
|
4639
|
+
if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
|
|
4640
|
+
for (const spaceId of spaces) {
|
|
4641
|
+
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);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
|
|
4645
|
+
for (const groupId of publishedGroups) {
|
|
4646
|
+
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);
|
|
4647
|
+
}
|
|
4648
|
+
} else {
|
|
4649
|
+
if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
|
|
4650
|
+
for (const spaceId of spaces) {
|
|
4651
|
+
this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
|
|
4655
|
+
for (const groupId of publishedGroups) {
|
|
4656
|
+
this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4520
4660
|
return {
|
|
4521
4661
|
action: "publish_memory",
|
|
4522
4662
|
success: true,
|
|
@@ -4598,6 +4738,21 @@ var SpaceService = class {
|
|
|
4598
4738
|
retracted: successfulRetractions,
|
|
4599
4739
|
failed: failedRetractions
|
|
4600
4740
|
});
|
|
4741
|
+
if (this.eventBus && successfulRetractions.length > 0) {
|
|
4742
|
+
const targets = [];
|
|
4743
|
+
if (successfulRetractions.some((r) => r.startsWith("spaces:"))) {
|
|
4744
|
+
for (const spaceId of spaces) {
|
|
4745
|
+
targets.push({ kind: "space", id: spaceId });
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
const retractedGroups = groups.filter((g) => successfulRetractions.some((r) => r === `group: ${g}`));
|
|
4749
|
+
for (const groupId of retractedGroups) {
|
|
4750
|
+
targets.push({ kind: "group", id: groupId });
|
|
4751
|
+
}
|
|
4752
|
+
if (targets.length > 0) {
|
|
4753
|
+
this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4601
4756
|
return {
|
|
4602
4757
|
action: "retract_memory",
|
|
4603
4758
|
success: true,
|
|
@@ -5527,6 +5682,132 @@ var INITIAL_PERCEPTION = {
|
|
|
5527
5682
|
last_updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5528
5683
|
};
|
|
5529
5684
|
|
|
5685
|
+
// node_modules/@prmichaelsen/remember-core/dist/webhooks/signing.js
|
|
5686
|
+
import { createHmac } from "node:crypto";
|
|
5687
|
+
function signWebhookPayload(webhookId, timestamp, body, secret) {
|
|
5688
|
+
const content = `${webhookId}.${timestamp}.${body}`;
|
|
5689
|
+
const hmac = createHmac("sha256", secret).update(content).digest("base64");
|
|
5690
|
+
return `v1,${hmac}`;
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5693
|
+
// node_modules/@prmichaelsen/remember-core/dist/webhooks/batched-webhook.service.js
|
|
5694
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
5695
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 1e3;
|
|
5696
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
5697
|
+
var SOURCE = "remember-core";
|
|
5698
|
+
var API_VERSION = "1";
|
|
5699
|
+
var BatchedWebhookService = class {
|
|
5700
|
+
logger;
|
|
5701
|
+
resolveEndpoint;
|
|
5702
|
+
maxBatchSize;
|
|
5703
|
+
flushIntervalMs;
|
|
5704
|
+
timeoutMs;
|
|
5705
|
+
onError;
|
|
5706
|
+
buffers = /* @__PURE__ */ new Map();
|
|
5707
|
+
constructor(logger2, config3) {
|
|
5708
|
+
this.logger = logger2;
|
|
5709
|
+
this.resolveEndpoint = config3.resolveEndpoint;
|
|
5710
|
+
this.maxBatchSize = config3.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
5711
|
+
this.flushIntervalMs = config3.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
5712
|
+
this.timeoutMs = config3.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
5713
|
+
this.onError = config3.onError;
|
|
5714
|
+
}
|
|
5715
|
+
emit(event, actor) {
|
|
5716
|
+
const ownerId = event.owner_id;
|
|
5717
|
+
const endpoints = this.resolveEndpoint(ownerId);
|
|
5718
|
+
if (endpoints.length === 0) {
|
|
5719
|
+
this.logger.debug?.("[BatchedWebhookService] no endpoints for owner, dropping event", {
|
|
5720
|
+
owner_id: ownerId,
|
|
5721
|
+
type: event.type
|
|
5722
|
+
});
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
const envelope = this.buildEnvelope(event, actor);
|
|
5726
|
+
for (const endpoint of endpoints) {
|
|
5727
|
+
const url = endpoint.url;
|
|
5728
|
+
let buffer = this.buffers.get(url);
|
|
5729
|
+
if (!buffer) {
|
|
5730
|
+
buffer = { endpoint, envelopes: [], timer: null };
|
|
5731
|
+
this.buffers.set(url, buffer);
|
|
5732
|
+
}
|
|
5733
|
+
buffer.envelopes.push(envelope);
|
|
5734
|
+
if (buffer.envelopes.length >= this.maxBatchSize) {
|
|
5735
|
+
this.flush(url);
|
|
5736
|
+
} else if (!buffer.timer) {
|
|
5737
|
+
buffer.timer = setTimeout(() => this.flush(url), this.flushIntervalMs);
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
flush(url) {
|
|
5742
|
+
const buffer = this.buffers.get(url);
|
|
5743
|
+
if (!buffer || buffer.envelopes.length === 0)
|
|
5744
|
+
return;
|
|
5745
|
+
const envelopes = buffer.envelopes;
|
|
5746
|
+
const endpoint = buffer.endpoint;
|
|
5747
|
+
if (buffer.timer) {
|
|
5748
|
+
clearTimeout(buffer.timer);
|
|
5749
|
+
}
|
|
5750
|
+
buffer.envelopes = [];
|
|
5751
|
+
buffer.timer = null;
|
|
5752
|
+
this.sendBatch(url, endpoint, envelopes).catch((err2) => {
|
|
5753
|
+
this.logger.error?.("[BatchedWebhookService] batch delivery failed", {
|
|
5754
|
+
error: err2,
|
|
5755
|
+
url,
|
|
5756
|
+
count: envelopes.length
|
|
5757
|
+
});
|
|
5758
|
+
this.onError?.(err2, envelopes);
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5761
|
+
dispose() {
|
|
5762
|
+
for (const url of this.buffers.keys()) {
|
|
5763
|
+
this.flush(url);
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
async sendBatch(url, endpoint, envelopes) {
|
|
5767
|
+
const batchId = v4_default();
|
|
5768
|
+
const batchTimestamp = Math.floor(Date.now() / 1e3);
|
|
5769
|
+
const body = JSON.stringify(envelopes);
|
|
5770
|
+
const signature = signWebhookPayload(batchId, batchTimestamp, body, endpoint.signingSecret);
|
|
5771
|
+
const controller = new AbortController();
|
|
5772
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
5773
|
+
try {
|
|
5774
|
+
const response = await fetch(url, {
|
|
5775
|
+
method: "POST",
|
|
5776
|
+
headers: {
|
|
5777
|
+
"Content-Type": "application/json",
|
|
5778
|
+
"webhook-id": batchId,
|
|
5779
|
+
"webhook-timestamp": String(batchTimestamp),
|
|
5780
|
+
"webhook-signature": signature,
|
|
5781
|
+
"x-webhook-batch": "true"
|
|
5782
|
+
},
|
|
5783
|
+
body,
|
|
5784
|
+
signal: controller.signal
|
|
5785
|
+
});
|
|
5786
|
+
if (!response.ok) {
|
|
5787
|
+
throw new Error(`Webhook batch delivery failed: HTTP ${response.status}`);
|
|
5788
|
+
}
|
|
5789
|
+
} finally {
|
|
5790
|
+
clearTimeout(timeout);
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
buildEnvelope(event, actor) {
|
|
5794
|
+
return {
|
|
5795
|
+
id: v4_default(),
|
|
5796
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
5797
|
+
source: SOURCE,
|
|
5798
|
+
api_version: API_VERSION,
|
|
5799
|
+
type: event.type,
|
|
5800
|
+
actor,
|
|
5801
|
+
data: event
|
|
5802
|
+
};
|
|
5803
|
+
}
|
|
5804
|
+
};
|
|
5805
|
+
|
|
5806
|
+
// node_modules/@prmichaelsen/remember-core/dist/webhooks/create.js
|
|
5807
|
+
function createBatchedWebhookService(logger2, config3) {
|
|
5808
|
+
return new BatchedWebhookService(logger2, config3);
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5530
5811
|
// src/weaviate/schema.ts
|
|
5531
5812
|
init_logger();
|
|
5532
5813
|
|
|
@@ -5645,6 +5926,15 @@ var tokenService = new ConfirmationTokenService(coreLogger);
|
|
|
5645
5926
|
var preferencesService = new PreferencesDatabaseService(coreLogger);
|
|
5646
5927
|
var moderationClient = process.env.ANTHROPIC_API_KEY ? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY }) : void 0;
|
|
5647
5928
|
var memoryIndexService = new MemoryIndexService(coreLogger);
|
|
5929
|
+
var eventBus = (() => {
|
|
5930
|
+
const url = process.env.REMEMBER_WEBHOOK_URL;
|
|
5931
|
+
const secret = process.env.REMEMBER_WEBHOOK_SECRET;
|
|
5932
|
+
if (!url || !secret)
|
|
5933
|
+
return void 0;
|
|
5934
|
+
return createBatchedWebhookService(coreLogger, {
|
|
5935
|
+
resolveEndpoint: () => [{ url, signingSecret: secret }]
|
|
5936
|
+
});
|
|
5937
|
+
})();
|
|
5648
5938
|
var coreServicesCache = /* @__PURE__ */ new Map();
|
|
5649
5939
|
function createCoreServices(userId) {
|
|
5650
5940
|
const cached = coreServicesCache.get(userId);
|
|
@@ -5658,7 +5948,7 @@ function createCoreServices(userId) {
|
|
|
5658
5948
|
weaviateClient
|
|
5659
5949
|
}),
|
|
5660
5950
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
5661
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient }),
|
|
5951
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient, eventBus }),
|
|
5662
5952
|
preferences: preferencesService,
|
|
5663
5953
|
token: tokenService
|
|
5664
5954
|
};
|
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/remember-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.1",
|
|
4
4
|
"description": "Multi-tenant memory system MCP server with vector search and relationships",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@google-cloud/vision": "^5.3.4",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
53
53
|
"@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
|
|
54
|
-
"@prmichaelsen/remember-core": "^0.
|
|
54
|
+
"@prmichaelsen/remember-core": "^0.54.0",
|
|
55
55
|
"dotenv": "^16.4.5",
|
|
56
56
|
"uuid": "^13.0.0",
|
|
57
57
|
"weaviate-client": "^3.2.0"
|
package/src/core-services.ts
CHANGED
|
@@ -14,8 +14,9 @@ import {
|
|
|
14
14
|
ConfirmationTokenService,
|
|
15
15
|
createLogger,
|
|
16
16
|
createModerationClient,
|
|
17
|
+
createBatchedWebhookService,
|
|
17
18
|
} from '@prmichaelsen/remember-core';
|
|
18
|
-
import type { Logger, ModerationClient } from '@prmichaelsen/remember-core';
|
|
19
|
+
import type { Logger, ModerationClient, EventBus } from '@prmichaelsen/remember-core';
|
|
19
20
|
import { getWeaviateClient } from './weaviate/client.js';
|
|
20
21
|
import { getMemoryCollection } from './weaviate/schema.js';
|
|
21
22
|
|
|
@@ -36,6 +37,16 @@ const moderationClient: ModerationClient | undefined = process.env.ANTHROPIC_API
|
|
|
36
37
|
: undefined;
|
|
37
38
|
const memoryIndexService = new MemoryIndexService(coreLogger);
|
|
38
39
|
|
|
40
|
+
// Webhook event bus — fans out events to all configured endpoints
|
|
41
|
+
const eventBus: EventBus | undefined = (() => {
|
|
42
|
+
const url = process.env.REMEMBER_WEBHOOK_URL;
|
|
43
|
+
const secret = process.env.REMEMBER_WEBHOOK_SECRET;
|
|
44
|
+
if (!url || !secret) return undefined;
|
|
45
|
+
return createBatchedWebhookService(coreLogger, {
|
|
46
|
+
resolveEndpoint: () => [{ url, signingSecret: secret }],
|
|
47
|
+
});
|
|
48
|
+
})();
|
|
49
|
+
|
|
39
50
|
/** Cached CoreServices per userId — avoids re-instantiation on every tool call */
|
|
40
51
|
const coreServicesCache = new Map<string, CoreServices>();
|
|
41
52
|
|
|
@@ -56,7 +67,7 @@ export function createCoreServices(userId: string): CoreServices {
|
|
|
56
67
|
weaviateClient,
|
|
57
68
|
}),
|
|
58
69
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
59
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient }),
|
|
70
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, memoryIndexService, { moderationClient, eventBus }),
|
|
60
71
|
preferences: preferencesService,
|
|
61
72
|
token: tokenService,
|
|
62
73
|
};
|
|
File without changes
|