@prmichaelsen/remember-mcp 3.14.19 → 3.14.21
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
CHANGED
|
@@ -5,6 +5,14 @@ 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.14.21] - 2026-03-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Wire LLM auto-moderation into SpaceService publish/revise flow (task-202)
|
|
13
|
+
- Create singleton `ModerationClient` from `ANTHROPIC_API_KEY` env var when present
|
|
14
|
+
- Content published to spaces is now screened by Claude Haiku before storage
|
|
15
|
+
|
|
8
16
|
## [3.14.14] - 2026-03-04
|
|
9
17
|
|
|
10
18
|
### Changed
|
package/agent/progress.yaml
CHANGED
|
@@ -246,10 +246,10 @@ milestones:
|
|
|
246
246
|
status: completed
|
|
247
247
|
progress: 100%
|
|
248
248
|
started: 2026-02-27
|
|
249
|
-
completed: 2026-
|
|
249
|
+
completed: 2026-03-06
|
|
250
250
|
estimated_weeks: 2
|
|
251
|
-
tasks_completed:
|
|
252
|
-
tasks_total:
|
|
251
|
+
tasks_completed: 7
|
|
252
|
+
tasks_total: 7
|
|
253
253
|
notes: |
|
|
254
254
|
✅ Content moderation lifecycle and per-space/group behavioral config.
|
|
255
255
|
✅ Design: agent/design/local.moderation-and-space-config.md (Implemented)
|
|
@@ -702,6 +702,19 @@ tasks:
|
|
|
702
702
|
✅ CHANGELOG, design doc status, version bump 3.9.0→3.10.0
|
|
703
703
|
📋 Verify all tests pass
|
|
704
704
|
|
|
705
|
+
- id: task-202
|
|
706
|
+
name: Wire LLM Moderation Client to SpaceService
|
|
707
|
+
status: completed
|
|
708
|
+
completed_date: 2026-03-06
|
|
709
|
+
file: agent/tasks/milestone-15-moderation-space-config/task-202-wire-llm-moderation-client.md
|
|
710
|
+
estimated_hours: 0.5
|
|
711
|
+
actual_hours: 0.25
|
|
712
|
+
dependencies: [task-179]
|
|
713
|
+
notes: |
|
|
714
|
+
📋 Connect createModerationClient (remember-core) to SpaceService in core-services.ts
|
|
715
|
+
📋 Auto-create when ANTHROPIC_API_KEY env var is present
|
|
716
|
+
📋 ~5 lines of code change
|
|
717
|
+
|
|
705
718
|
milestone_7:
|
|
706
719
|
- id: task-180
|
|
707
720
|
name: Access Result & Permission Types
|
package/agent/tasks/milestone-15-moderation-space-config/task-202-wire-llm-moderation-client.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Task 202: Wire LLM Moderation Client to SpaceService
|
|
2
|
+
|
|
3
|
+
**Milestone**: M15 - Moderation & Space Config (follow-up)
|
|
4
|
+
**Status**: completed
|
|
5
|
+
**Estimated Hours**: 0.5
|
|
6
|
+
**Priority**: P1
|
|
7
|
+
**Dependencies**: M15 (completed), remember-core moderation.service.ts
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Objective
|
|
12
|
+
|
|
13
|
+
Connect the `createModerationClient` from `@prmichaelsen/remember-core` to the `SpaceService` in `remember-mcp`, so that content published to spaces is automatically screened by Claude Haiku before being stored.
|
|
14
|
+
|
|
15
|
+
Currently, all moderation infrastructure exists but is disconnected:
|
|
16
|
+
- `remember-core` exports `createModerationClient()` (calls Anthropic Messages API with Haiku)
|
|
17
|
+
- `SpaceService` accepts `{ moderationClient }` in its constructor options
|
|
18
|
+
- `SpaceService.checkModeration()` calls `moderationClient.moderate()` on publish/revise
|
|
19
|
+
- But `createCoreServices()` in `remember-mcp` never creates or passes a moderation client
|
|
20
|
+
|
|
21
|
+
## Context
|
|
22
|
+
|
|
23
|
+
- `remember-core/src/services/moderation.service.ts` — factory + types
|
|
24
|
+
- `remember-core/src/services/space.service.ts:237` — constructor accepts `options?.moderationClient`
|
|
25
|
+
- `remember-mcp/src/core-services.ts:50` — SpaceService created without moderation client
|
|
26
|
+
- `remember-mcp-server/.env` already has `ANTHROPIC_API_KEY` set
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
|
|
30
|
+
### 1. Edit `src/core-services.ts`
|
|
31
|
+
|
|
32
|
+
- Import `createModerationClient` from `@prmichaelsen/remember-core`
|
|
33
|
+
- Create a singleton moderation client (only when `ANTHROPIC_API_KEY` env var is present)
|
|
34
|
+
- Pass `{ moderationClient }` as the 6th argument to `new SpaceService()`
|
|
35
|
+
|
|
36
|
+
### 2. Build and verify
|
|
37
|
+
|
|
38
|
+
- Run `npm run build` (or equivalent)
|
|
39
|
+
- Verify TypeScript compiles without errors
|
|
40
|
+
|
|
41
|
+
### 3. Run tests
|
|
42
|
+
|
|
43
|
+
- Run existing test suite to ensure no regressions
|
|
44
|
+
- Moderation client is optional, so existing tests should pass unaffected
|
|
45
|
+
|
|
46
|
+
### 4. Version bump
|
|
47
|
+
|
|
48
|
+
- Bump patch version in package.json
|
|
49
|
+
- Update CHANGELOG.md
|
|
50
|
+
|
|
51
|
+
## Verification
|
|
52
|
+
|
|
53
|
+
- [ ] `createModerationClient` imported from remember-core
|
|
54
|
+
- [ ] Moderation client created conditionally (only when `ANTHROPIC_API_KEY` is set)
|
|
55
|
+
- [ ] Moderation client passed to SpaceService constructor
|
|
56
|
+
- [ ] TypeScript compiles without errors
|
|
57
|
+
- [ ] Existing tests pass
|
|
58
|
+
- [ ] Version bumped
|
|
59
|
+
|
|
60
|
+
## Downstream
|
|
61
|
+
|
|
62
|
+
After this ships, `remember-mcp-server` just needs a dependency bump — no code changes required. `ANTHROPIC_API_KEY` is already configured in its `.env`.
|
package/dist/server-factory.js
CHANGED
|
@@ -886,7 +886,7 @@ var DEFAULT_PREFERENCES = {
|
|
|
886
886
|
share_with_memories: true
|
|
887
887
|
},
|
|
888
888
|
privacy: {
|
|
889
|
-
default_trust_level:
|
|
889
|
+
default_trust_level: 2,
|
|
890
890
|
allow_cross_user_access: false,
|
|
891
891
|
auto_approve_requests: false,
|
|
892
892
|
audit_logging: true
|
|
@@ -905,6 +905,14 @@ var DEFAULT_PREFERENCES = {
|
|
|
905
905
|
}
|
|
906
906
|
};
|
|
907
907
|
|
|
908
|
+
// node_modules/@prmichaelsen/remember-core/dist/types/rating.types.js
|
|
909
|
+
var RATING_MIN_THRESHOLD = 5;
|
|
910
|
+
function computeRatingAvg(ratingSum, ratingCount) {
|
|
911
|
+
if (ratingCount < RATING_MIN_THRESHOLD)
|
|
912
|
+
return null;
|
|
913
|
+
return ratingSum / ratingCount;
|
|
914
|
+
}
|
|
915
|
+
|
|
908
916
|
// node_modules/@prmichaelsen/remember-core/dist/types/space.types.js
|
|
909
917
|
var SUPPORTED_SPACES = [
|
|
910
918
|
"the_void",
|
|
@@ -1361,6 +1369,35 @@ var DebugLevel2;
|
|
|
1361
1369
|
DebugLevel3[DebugLevel3["TRACE"] = 5] = "TRACE";
|
|
1362
1370
|
})(DebugLevel2 || (DebugLevel2 = {}));
|
|
1363
1371
|
|
|
1372
|
+
// node_modules/@prmichaelsen/remember-core/dist/errors/base.error.js
|
|
1373
|
+
var AppError = class extends Error {
|
|
1374
|
+
context;
|
|
1375
|
+
constructor(message, context = {}) {
|
|
1376
|
+
super(message);
|
|
1377
|
+
this.context = context;
|
|
1378
|
+
this.name = this.constructor.name;
|
|
1379
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1380
|
+
}
|
|
1381
|
+
toJSON() {
|
|
1382
|
+
return {
|
|
1383
|
+
kind: this.kind,
|
|
1384
|
+
name: this.name,
|
|
1385
|
+
message: this.message,
|
|
1386
|
+
context: this.context
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// node_modules/@prmichaelsen/remember-core/dist/errors/app-errors.js
|
|
1392
|
+
var ValidationError = class extends AppError {
|
|
1393
|
+
fields;
|
|
1394
|
+
kind = "validation";
|
|
1395
|
+
constructor(message, fields = {}) {
|
|
1396
|
+
super(message, { fields });
|
|
1397
|
+
this.fields = fields;
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1364
1401
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
|
|
1365
1402
|
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
|
1366
1403
|
|
|
@@ -1637,6 +1674,9 @@ function buildDocTypeFilters(collection, docType, filters) {
|
|
|
1637
1674
|
if (filters?.relationship_count_max !== void 0) {
|
|
1638
1675
|
filterList.push(collection.filter.byProperty("relationship_count").lessOrEqual(filters.relationship_count_max));
|
|
1639
1676
|
}
|
|
1677
|
+
if (filters?.rating_min !== void 0) {
|
|
1678
|
+
filterList.push(collection.filter.byProperty("rating_bayesian").greaterOrEqual(filters.rating_min));
|
|
1679
|
+
}
|
|
1640
1680
|
if (filters?.tags && filters.tags.length > 0) {
|
|
1641
1681
|
filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
|
|
1642
1682
|
}
|
|
@@ -2004,6 +2044,9 @@ var ALL_MEMORY_PROPERTIES = [
|
|
|
2004
2044
|
"observation",
|
|
2005
2045
|
"strength",
|
|
2006
2046
|
"source",
|
|
2047
|
+
"rating_sum",
|
|
2048
|
+
"rating_count",
|
|
2049
|
+
"rating_bayesian",
|
|
2007
2050
|
"access_count",
|
|
2008
2051
|
"last_accessed_at",
|
|
2009
2052
|
"tags",
|
|
@@ -2053,6 +2096,11 @@ function normalizeDoc(doc) {
|
|
|
2053
2096
|
if ("trust_score" in doc) {
|
|
2054
2097
|
doc.trust_score = normalizeTrustScore(doc.trust_score);
|
|
2055
2098
|
}
|
|
2099
|
+
const ratingSum = doc.rating_sum;
|
|
2100
|
+
const ratingCount = doc.rating_count;
|
|
2101
|
+
if (ratingSum !== void 0 && ratingCount !== void 0) {
|
|
2102
|
+
doc.rating_avg = computeRatingAvg(ratingSum, ratingCount);
|
|
2103
|
+
}
|
|
2056
2104
|
return doc;
|
|
2057
2105
|
}
|
|
2058
2106
|
var MemoryService = class {
|
|
@@ -2125,12 +2173,16 @@ var MemoryService = class {
|
|
|
2125
2173
|
summary: input.title,
|
|
2126
2174
|
content_type: contentType,
|
|
2127
2175
|
weight: input.weight ?? 0.5,
|
|
2128
|
-
trust_score: input.trust ?? TrustLevel.INTERNAL,
|
|
2176
|
+
trust_score: normalizeTrustScore(input.trust ?? TrustLevel.INTERNAL),
|
|
2129
2177
|
confidence: 1,
|
|
2130
2178
|
context_summary: input.context_summary || "Memory created",
|
|
2131
2179
|
context_conversation_id: input.context_conversation_id,
|
|
2132
2180
|
relationship_ids: [],
|
|
2133
2181
|
relationship_count: 0,
|
|
2182
|
+
rating_sum: 0,
|
|
2183
|
+
rating_count: 0,
|
|
2184
|
+
rating_bayesian: 3,
|
|
2185
|
+
// (0 + 15) / (0 + 5) = 3.0 (prior mean)
|
|
2134
2186
|
access_count: 0,
|
|
2135
2187
|
last_accessed_at: now,
|
|
2136
2188
|
created_at: now,
|
|
@@ -2294,6 +2346,47 @@ var MemoryService = class {
|
|
|
2294
2346
|
limit
|
|
2295
2347
|
};
|
|
2296
2348
|
}
|
|
2349
|
+
// ── By Rating (Bayesian average) ─────────────────────────────────
|
|
2350
|
+
async byRating(input) {
|
|
2351
|
+
const limit = input.limit ?? 50;
|
|
2352
|
+
const offset = input.offset ?? 0;
|
|
2353
|
+
const direction = input.direction ?? "desc";
|
|
2354
|
+
const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
|
|
2355
|
+
const ghostFilters = [];
|
|
2356
|
+
if (input.ghost_context) {
|
|
2357
|
+
ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
|
|
2358
|
+
}
|
|
2359
|
+
if (!input.ghost_context?.include_ghost_content) {
|
|
2360
|
+
ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
|
|
2361
|
+
}
|
|
2362
|
+
const executeQuery = async (useDeletedFilter) => {
|
|
2363
|
+
const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
|
|
2364
|
+
const combinedFilters = combineFiltersWithAnd([deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null));
|
|
2365
|
+
const queryOptions = {
|
|
2366
|
+
limit: limit + offset,
|
|
2367
|
+
sort: this.collection.sort.byProperty("rating_bayesian", direction === "asc")
|
|
2368
|
+
};
|
|
2369
|
+
if (combinedFilters) {
|
|
2370
|
+
queryOptions.filters = combinedFilters;
|
|
2371
|
+
}
|
|
2372
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2373
|
+
};
|
|
2374
|
+
const results = await this.retryWithoutDeletedFilter(executeQuery);
|
|
2375
|
+
const paginated = results.objects.slice(offset);
|
|
2376
|
+
const memories = [];
|
|
2377
|
+
for (const obj of paginated) {
|
|
2378
|
+
const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
|
|
2379
|
+
if (doc.doc_type === "memory") {
|
|
2380
|
+
memories.push(doc);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
return {
|
|
2384
|
+
memories,
|
|
2385
|
+
total: memories.length,
|
|
2386
|
+
offset,
|
|
2387
|
+
limit
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2297
2390
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2298
2391
|
async findSimilar(input) {
|
|
2299
2392
|
if (!input.memory_id && !input.text)
|
|
@@ -2869,6 +2962,10 @@ var COMMON_MEMORY_PROPERTIES = [
|
|
|
2869
2962
|
{ name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
|
|
2870
2963
|
{ name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
|
|
2871
2964
|
{ name: "relationship_count", dataType: configure.dataType.INT },
|
|
2965
|
+
// Rating aggregates (denormalized from Firestore individual ratings)
|
|
2966
|
+
{ name: "rating_sum", dataType: configure.dataType.INT },
|
|
2967
|
+
{ name: "rating_count", dataType: configure.dataType.INT },
|
|
2968
|
+
{ name: "rating_bayesian", dataType: configure.dataType.NUMBER },
|
|
2872
2969
|
// Access tracking
|
|
2873
2970
|
{ name: "access_count", dataType: configure.dataType.NUMBER },
|
|
2874
2971
|
{ name: "last_accessed_at", dataType: configure.dataType.DATE },
|
|
@@ -3045,12 +3142,26 @@ var SpaceService = class {
|
|
|
3045
3142
|
userId;
|
|
3046
3143
|
confirmationTokenService;
|
|
3047
3144
|
logger;
|
|
3048
|
-
|
|
3145
|
+
moderationClient;
|
|
3146
|
+
constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, options) {
|
|
3049
3147
|
this.weaviateClient = weaviateClient;
|
|
3050
3148
|
this.userCollection = userCollection;
|
|
3051
3149
|
this.userId = userId;
|
|
3052
3150
|
this.confirmationTokenService = confirmationTokenService;
|
|
3053
3151
|
this.logger = logger2;
|
|
3152
|
+
this.moderationClient = options?.moderationClient;
|
|
3153
|
+
}
|
|
3154
|
+
// ── Content moderation helper ────────────────────────────────────────
|
|
3155
|
+
async checkModeration(content) {
|
|
3156
|
+
if (!this.moderationClient)
|
|
3157
|
+
return;
|
|
3158
|
+
const result = await this.moderationClient.moderate(content);
|
|
3159
|
+
if (!result.pass) {
|
|
3160
|
+
throw new ValidationError(result.reason, {
|
|
3161
|
+
moderation: ["blocked"],
|
|
3162
|
+
...result.category ? { category: [result.category] } : {}
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3054
3165
|
}
|
|
3055
3166
|
// ── Publish (phase 1: generate confirmation token) ──────────────────
|
|
3056
3167
|
async publish(input) {
|
|
@@ -3085,6 +3196,7 @@ var SpaceService = class {
|
|
|
3085
3196
|
throw new Error(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
|
|
3086
3197
|
}
|
|
3087
3198
|
}
|
|
3199
|
+
await this.checkModeration(memory.properties.content);
|
|
3088
3200
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
|
|
3089
3201
|
memory_id: input.memory_id,
|
|
3090
3202
|
spaces,
|
|
@@ -3151,6 +3263,7 @@ var SpaceService = class {
|
|
|
3151
3263
|
if (spaceIds.length === 0 && groupIds.length === 0) {
|
|
3152
3264
|
throw new Error("Memory has no published copies to revise. Publish first with publish().");
|
|
3153
3265
|
}
|
|
3266
|
+
await this.checkModeration(memory.properties.content);
|
|
3154
3267
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
|
|
3155
3268
|
memory_id: input.memory_id,
|
|
3156
3269
|
space_ids: spaceIds,
|
|
@@ -3718,6 +3831,111 @@ var REM_STATE_COLLECTION = `${BASE}.rem_state`;
|
|
|
3718
3831
|
// node_modules/@prmichaelsen/remember-core/dist/services/rem.clustering.js
|
|
3719
3832
|
import { Filters as Filters5 } from "weaviate-client";
|
|
3720
3833
|
|
|
3834
|
+
// node_modules/@prmichaelsen/remember-core/dist/services/moderation.service.js
|
|
3835
|
+
import { createHash as createHash2 } from "crypto";
|
|
3836
|
+
function buildModerationPrompt(content) {
|
|
3837
|
+
return `You are a content moderation classifier. Evaluate the following user-generated content and determine if it should be ALLOWED or BLOCKED from a public space.
|
|
3838
|
+
|
|
3839
|
+
You must be LENIENT. Most content should pass. Only block content that is clearly and explicitly in one of these categories:
|
|
3840
|
+
|
|
3841
|
+
BLOCK categories:
|
|
3842
|
+
|
|
3843
|
+
1. HATE SPEECH \u2014 Explicit dehumanization of ethnic/racial/religious groups, calls for violence against protected groups, racial slurs used to demean.
|
|
3844
|
+
Examples that FAIL:
|
|
3845
|
+
- "All [ethnic group] should be exterminated. They are subhuman parasites."
|
|
3846
|
+
- Explicit Nazi ideology or white supremacist recruitment
|
|
3847
|
+
- "[Racial slur] don't deserve rights"
|
|
3848
|
+
- Genocide celebration ("The Holocaust was a good thing")
|
|
3849
|
+
|
|
3850
|
+
2. VIOLENCE INCITEMENT \u2014 Direct threats against specific people or locations, incitement to commit specific acts of mass violence.
|
|
3851
|
+
Examples that FAIL:
|
|
3852
|
+
- "I'm going to find [person] and make them pay. They won't see it coming."
|
|
3853
|
+
- "Someone should shoot up [location]. That would teach them."
|
|
3854
|
+
Examples that PASS:
|
|
3855
|
+
- Instructions or information about weapons (legally protected speech without incitement)
|
|
3856
|
+
- Historical/philosophical discussion of violence
|
|
3857
|
+
- Fictional violence (games, stories, D&D campaigns)
|
|
3858
|
+
- Colloquial expressions ("I could kill for a burger")
|
|
3859
|
+
|
|
3860
|
+
3. CSAM \u2014 Any sexual content involving minors. Zero tolerance.
|
|
3861
|
+
|
|
3862
|
+
4. SELF-HARM ENCOURAGEMENT \u2014 Detailed instructions or active encouragement for self-harm or suicide.
|
|
3863
|
+
Examples that PASS:
|
|
3864
|
+
- Discussions about depression or mental health struggles
|
|
3865
|
+
- Jokes about suicide or dark humor about self-harm
|
|
3866
|
+
- Journaling about difficult feelings
|
|
3867
|
+
|
|
3868
|
+
ALWAYS ALLOW:
|
|
3869
|
+
- Harsh political opinions, criticism of governments or public figures
|
|
3870
|
+
- Edgy humor, dark comedy, offensive jokes (without targeted dehumanization)
|
|
3871
|
+
- Strong opinions about religion, ideology, or social issues
|
|
3872
|
+
- Profanity and vulgar language
|
|
3873
|
+
- Controversial or uncomfortable topics
|
|
3874
|
+
- Educational/historical content about atrocities
|
|
3875
|
+
- The French Revolution, violent revolution as philosophical concept
|
|
3876
|
+
|
|
3877
|
+
Content to evaluate:
|
|
3878
|
+
---
|
|
3879
|
+
${content}
|
|
3880
|
+
---
|
|
3881
|
+
|
|
3882
|
+
Respond with ONLY valid JSON:
|
|
3883
|
+
{"pass":true}
|
|
3884
|
+
OR
|
|
3885
|
+
{"pass":false,"reason":"<specific, human-friendly explanation of why this was blocked>","category":"<hate_speech|extremism|violence_incitement|csam|self_harm_encouragement>"}`;
|
|
3886
|
+
}
|
|
3887
|
+
var DEFAULT_CACHE_MAX = 1e3;
|
|
3888
|
+
function hashContent(content) {
|
|
3889
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
3890
|
+
}
|
|
3891
|
+
function createModerationClient(options) {
|
|
3892
|
+
const model = options.model ?? "claude-haiku-4-5-20251001";
|
|
3893
|
+
const cacheMax = options.cacheMax ?? DEFAULT_CACHE_MAX;
|
|
3894
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3895
|
+
return {
|
|
3896
|
+
async moderate(content) {
|
|
3897
|
+
const hash = hashContent(content);
|
|
3898
|
+
const cached = cache.get(hash);
|
|
3899
|
+
if (cached)
|
|
3900
|
+
return cached;
|
|
3901
|
+
try {
|
|
3902
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
3903
|
+
method: "POST",
|
|
3904
|
+
headers: {
|
|
3905
|
+
"Content-Type": "application/json",
|
|
3906
|
+
"x-api-key": options.apiKey,
|
|
3907
|
+
"anthropic-version": "2023-06-01"
|
|
3908
|
+
},
|
|
3909
|
+
body: JSON.stringify({
|
|
3910
|
+
model,
|
|
3911
|
+
max_tokens: 256,
|
|
3912
|
+
messages: [{ role: "user", content: buildModerationPrompt(content) }]
|
|
3913
|
+
})
|
|
3914
|
+
});
|
|
3915
|
+
if (!response.ok) {
|
|
3916
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
3917
|
+
}
|
|
3918
|
+
const data = await response.json();
|
|
3919
|
+
const text = data.content?.[0]?.text ?? "";
|
|
3920
|
+
const parsed = JSON.parse(text);
|
|
3921
|
+
const result = {
|
|
3922
|
+
pass: parsed.pass === true,
|
|
3923
|
+
reason: parsed.reason ?? "",
|
|
3924
|
+
category: parsed.pass ? void 0 : parsed.category
|
|
3925
|
+
};
|
|
3926
|
+
if (cache.size >= cacheMax) {
|
|
3927
|
+
const oldest = cache.keys().next().value;
|
|
3928
|
+
cache.delete(oldest);
|
|
3929
|
+
}
|
|
3930
|
+
cache.set(hash, result);
|
|
3931
|
+
return result;
|
|
3932
|
+
} catch {
|
|
3933
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3721
3939
|
// src/weaviate/schema.ts
|
|
3722
3940
|
init_logger();
|
|
3723
3941
|
|
|
@@ -3834,6 +4052,7 @@ function getMemoryCollection(userId) {
|
|
|
3834
4052
|
var coreLogger = createLogger("info");
|
|
3835
4053
|
var tokenService = new ConfirmationTokenService(coreLogger);
|
|
3836
4054
|
var preferencesService = new PreferencesDatabaseService(coreLogger);
|
|
4055
|
+
var moderationClient = process.env.ANTHROPIC_API_KEY ? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY }) : void 0;
|
|
3837
4056
|
var coreServicesCache = /* @__PURE__ */ new Map();
|
|
3838
4057
|
function createCoreServices(userId) {
|
|
3839
4058
|
const cached = coreServicesCache.get(userId);
|
|
@@ -3844,7 +4063,7 @@ function createCoreServices(userId) {
|
|
|
3844
4063
|
const services = {
|
|
3845
4064
|
memory: new MemoryService(collection, userId, coreLogger),
|
|
3846
4065
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
3847
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger),
|
|
4066
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, { moderationClient }),
|
|
3848
4067
|
preferences: preferencesService,
|
|
3849
4068
|
token: tokenService
|
|
3850
4069
|
};
|
package/dist/server.js
CHANGED
|
@@ -890,7 +890,7 @@ var DEFAULT_PREFERENCES = {
|
|
|
890
890
|
share_with_memories: true
|
|
891
891
|
},
|
|
892
892
|
privacy: {
|
|
893
|
-
default_trust_level:
|
|
893
|
+
default_trust_level: 2,
|
|
894
894
|
allow_cross_user_access: false,
|
|
895
895
|
auto_approve_requests: false,
|
|
896
896
|
audit_logging: true
|
|
@@ -909,6 +909,14 @@ var DEFAULT_PREFERENCES = {
|
|
|
909
909
|
}
|
|
910
910
|
};
|
|
911
911
|
|
|
912
|
+
// node_modules/@prmichaelsen/remember-core/dist/types/rating.types.js
|
|
913
|
+
var RATING_MIN_THRESHOLD = 5;
|
|
914
|
+
function computeRatingAvg(ratingSum, ratingCount) {
|
|
915
|
+
if (ratingCount < RATING_MIN_THRESHOLD)
|
|
916
|
+
return null;
|
|
917
|
+
return ratingSum / ratingCount;
|
|
918
|
+
}
|
|
919
|
+
|
|
912
920
|
// node_modules/@prmichaelsen/remember-core/dist/types/space.types.js
|
|
913
921
|
var SUPPORTED_SPACES = [
|
|
914
922
|
"the_void",
|
|
@@ -1365,6 +1373,35 @@ var DebugLevel2;
|
|
|
1365
1373
|
DebugLevel3[DebugLevel3["TRACE"] = 5] = "TRACE";
|
|
1366
1374
|
})(DebugLevel2 || (DebugLevel2 = {}));
|
|
1367
1375
|
|
|
1376
|
+
// node_modules/@prmichaelsen/remember-core/dist/errors/base.error.js
|
|
1377
|
+
var AppError = class extends Error {
|
|
1378
|
+
context;
|
|
1379
|
+
constructor(message, context = {}) {
|
|
1380
|
+
super(message);
|
|
1381
|
+
this.context = context;
|
|
1382
|
+
this.name = this.constructor.name;
|
|
1383
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1384
|
+
}
|
|
1385
|
+
toJSON() {
|
|
1386
|
+
return {
|
|
1387
|
+
kind: this.kind,
|
|
1388
|
+
name: this.name,
|
|
1389
|
+
message: this.message,
|
|
1390
|
+
context: this.context
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
// node_modules/@prmichaelsen/remember-core/dist/errors/app-errors.js
|
|
1396
|
+
var ValidationError = class extends AppError {
|
|
1397
|
+
fields;
|
|
1398
|
+
kind = "validation";
|
|
1399
|
+
constructor(message, fields = {}) {
|
|
1400
|
+
super(message, { fields });
|
|
1401
|
+
this.fields = fields;
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1368
1405
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
|
|
1369
1406
|
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
|
1370
1407
|
|
|
@@ -1641,6 +1678,9 @@ function buildDocTypeFilters(collection, docType, filters) {
|
|
|
1641
1678
|
if (filters?.relationship_count_max !== void 0) {
|
|
1642
1679
|
filterList.push(collection.filter.byProperty("relationship_count").lessOrEqual(filters.relationship_count_max));
|
|
1643
1680
|
}
|
|
1681
|
+
if (filters?.rating_min !== void 0) {
|
|
1682
|
+
filterList.push(collection.filter.byProperty("rating_bayesian").greaterOrEqual(filters.rating_min));
|
|
1683
|
+
}
|
|
1644
1684
|
if (filters?.tags && filters.tags.length > 0) {
|
|
1645
1685
|
filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
|
|
1646
1686
|
}
|
|
@@ -2008,6 +2048,9 @@ var ALL_MEMORY_PROPERTIES = [
|
|
|
2008
2048
|
"observation",
|
|
2009
2049
|
"strength",
|
|
2010
2050
|
"source",
|
|
2051
|
+
"rating_sum",
|
|
2052
|
+
"rating_count",
|
|
2053
|
+
"rating_bayesian",
|
|
2011
2054
|
"access_count",
|
|
2012
2055
|
"last_accessed_at",
|
|
2013
2056
|
"tags",
|
|
@@ -2057,6 +2100,11 @@ function normalizeDoc(doc) {
|
|
|
2057
2100
|
if ("trust_score" in doc) {
|
|
2058
2101
|
doc.trust_score = normalizeTrustScore(doc.trust_score);
|
|
2059
2102
|
}
|
|
2103
|
+
const ratingSum = doc.rating_sum;
|
|
2104
|
+
const ratingCount = doc.rating_count;
|
|
2105
|
+
if (ratingSum !== void 0 && ratingCount !== void 0) {
|
|
2106
|
+
doc.rating_avg = computeRatingAvg(ratingSum, ratingCount);
|
|
2107
|
+
}
|
|
2060
2108
|
return doc;
|
|
2061
2109
|
}
|
|
2062
2110
|
var MemoryService = class {
|
|
@@ -2129,12 +2177,16 @@ var MemoryService = class {
|
|
|
2129
2177
|
summary: input.title,
|
|
2130
2178
|
content_type: contentType,
|
|
2131
2179
|
weight: input.weight ?? 0.5,
|
|
2132
|
-
trust_score: input.trust ?? TrustLevel.INTERNAL,
|
|
2180
|
+
trust_score: normalizeTrustScore(input.trust ?? TrustLevel.INTERNAL),
|
|
2133
2181
|
confidence: 1,
|
|
2134
2182
|
context_summary: input.context_summary || "Memory created",
|
|
2135
2183
|
context_conversation_id: input.context_conversation_id,
|
|
2136
2184
|
relationship_ids: [],
|
|
2137
2185
|
relationship_count: 0,
|
|
2186
|
+
rating_sum: 0,
|
|
2187
|
+
rating_count: 0,
|
|
2188
|
+
rating_bayesian: 3,
|
|
2189
|
+
// (0 + 15) / (0 + 5) = 3.0 (prior mean)
|
|
2138
2190
|
access_count: 0,
|
|
2139
2191
|
last_accessed_at: now,
|
|
2140
2192
|
created_at: now,
|
|
@@ -2298,6 +2350,47 @@ var MemoryService = class {
|
|
|
2298
2350
|
limit
|
|
2299
2351
|
};
|
|
2300
2352
|
}
|
|
2353
|
+
// ── By Rating (Bayesian average) ─────────────────────────────────
|
|
2354
|
+
async byRating(input) {
|
|
2355
|
+
const limit = input.limit ?? 50;
|
|
2356
|
+
const offset = input.offset ?? 0;
|
|
2357
|
+
const direction = input.direction ?? "desc";
|
|
2358
|
+
const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
|
|
2359
|
+
const ghostFilters = [];
|
|
2360
|
+
if (input.ghost_context) {
|
|
2361
|
+
ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
|
|
2362
|
+
}
|
|
2363
|
+
if (!input.ghost_context?.include_ghost_content) {
|
|
2364
|
+
ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
|
|
2365
|
+
}
|
|
2366
|
+
const executeQuery = async (useDeletedFilter) => {
|
|
2367
|
+
const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
|
|
2368
|
+
const combinedFilters = combineFiltersWithAnd([deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null));
|
|
2369
|
+
const queryOptions = {
|
|
2370
|
+
limit: limit + offset,
|
|
2371
|
+
sort: this.collection.sort.byProperty("rating_bayesian", direction === "asc")
|
|
2372
|
+
};
|
|
2373
|
+
if (combinedFilters) {
|
|
2374
|
+
queryOptions.filters = combinedFilters;
|
|
2375
|
+
}
|
|
2376
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2377
|
+
};
|
|
2378
|
+
const results = await this.retryWithoutDeletedFilter(executeQuery);
|
|
2379
|
+
const paginated = results.objects.slice(offset);
|
|
2380
|
+
const memories = [];
|
|
2381
|
+
for (const obj of paginated) {
|
|
2382
|
+
const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
|
|
2383
|
+
if (doc.doc_type === "memory") {
|
|
2384
|
+
memories.push(doc);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return {
|
|
2388
|
+
memories,
|
|
2389
|
+
total: memories.length,
|
|
2390
|
+
offset,
|
|
2391
|
+
limit
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2301
2394
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2302
2395
|
async findSimilar(input) {
|
|
2303
2396
|
if (!input.memory_id && !input.text)
|
|
@@ -2873,6 +2966,10 @@ var COMMON_MEMORY_PROPERTIES = [
|
|
|
2873
2966
|
{ name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
|
|
2874
2967
|
{ name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
|
|
2875
2968
|
{ name: "relationship_count", dataType: configure.dataType.INT },
|
|
2969
|
+
// Rating aggregates (denormalized from Firestore individual ratings)
|
|
2970
|
+
{ name: "rating_sum", dataType: configure.dataType.INT },
|
|
2971
|
+
{ name: "rating_count", dataType: configure.dataType.INT },
|
|
2972
|
+
{ name: "rating_bayesian", dataType: configure.dataType.NUMBER },
|
|
2876
2973
|
// Access tracking
|
|
2877
2974
|
{ name: "access_count", dataType: configure.dataType.NUMBER },
|
|
2878
2975
|
{ name: "last_accessed_at", dataType: configure.dataType.DATE },
|
|
@@ -3049,12 +3146,26 @@ var SpaceService = class {
|
|
|
3049
3146
|
userId;
|
|
3050
3147
|
confirmationTokenService;
|
|
3051
3148
|
logger;
|
|
3052
|
-
|
|
3149
|
+
moderationClient;
|
|
3150
|
+
constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, options) {
|
|
3053
3151
|
this.weaviateClient = weaviateClient;
|
|
3054
3152
|
this.userCollection = userCollection;
|
|
3055
3153
|
this.userId = userId;
|
|
3056
3154
|
this.confirmationTokenService = confirmationTokenService;
|
|
3057
3155
|
this.logger = logger2;
|
|
3156
|
+
this.moderationClient = options?.moderationClient;
|
|
3157
|
+
}
|
|
3158
|
+
// ── Content moderation helper ────────────────────────────────────────
|
|
3159
|
+
async checkModeration(content) {
|
|
3160
|
+
if (!this.moderationClient)
|
|
3161
|
+
return;
|
|
3162
|
+
const result = await this.moderationClient.moderate(content);
|
|
3163
|
+
if (!result.pass) {
|
|
3164
|
+
throw new ValidationError(result.reason, {
|
|
3165
|
+
moderation: ["blocked"],
|
|
3166
|
+
...result.category ? { category: [result.category] } : {}
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3058
3169
|
}
|
|
3059
3170
|
// ── Publish (phase 1: generate confirmation token) ──────────────────
|
|
3060
3171
|
async publish(input) {
|
|
@@ -3089,6 +3200,7 @@ var SpaceService = class {
|
|
|
3089
3200
|
throw new Error(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
|
|
3090
3201
|
}
|
|
3091
3202
|
}
|
|
3203
|
+
await this.checkModeration(memory.properties.content);
|
|
3092
3204
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
|
|
3093
3205
|
memory_id: input.memory_id,
|
|
3094
3206
|
spaces,
|
|
@@ -3155,6 +3267,7 @@ var SpaceService = class {
|
|
|
3155
3267
|
if (spaceIds.length === 0 && groupIds.length === 0) {
|
|
3156
3268
|
throw new Error("Memory has no published copies to revise. Publish first with publish().");
|
|
3157
3269
|
}
|
|
3270
|
+
await this.checkModeration(memory.properties.content);
|
|
3158
3271
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
|
|
3159
3272
|
memory_id: input.memory_id,
|
|
3160
3273
|
space_ids: spaceIds,
|
|
@@ -3722,6 +3835,111 @@ var REM_STATE_COLLECTION = `${BASE}.rem_state`;
|
|
|
3722
3835
|
// node_modules/@prmichaelsen/remember-core/dist/services/rem.clustering.js
|
|
3723
3836
|
import { Filters as Filters5 } from "weaviate-client";
|
|
3724
3837
|
|
|
3838
|
+
// node_modules/@prmichaelsen/remember-core/dist/services/moderation.service.js
|
|
3839
|
+
import { createHash as createHash2 } from "crypto";
|
|
3840
|
+
function buildModerationPrompt(content) {
|
|
3841
|
+
return `You are a content moderation classifier. Evaluate the following user-generated content and determine if it should be ALLOWED or BLOCKED from a public space.
|
|
3842
|
+
|
|
3843
|
+
You must be LENIENT. Most content should pass. Only block content that is clearly and explicitly in one of these categories:
|
|
3844
|
+
|
|
3845
|
+
BLOCK categories:
|
|
3846
|
+
|
|
3847
|
+
1. HATE SPEECH \u2014 Explicit dehumanization of ethnic/racial/religious groups, calls for violence against protected groups, racial slurs used to demean.
|
|
3848
|
+
Examples that FAIL:
|
|
3849
|
+
- "All [ethnic group] should be exterminated. They are subhuman parasites."
|
|
3850
|
+
- Explicit Nazi ideology or white supremacist recruitment
|
|
3851
|
+
- "[Racial slur] don't deserve rights"
|
|
3852
|
+
- Genocide celebration ("The Holocaust was a good thing")
|
|
3853
|
+
|
|
3854
|
+
2. VIOLENCE INCITEMENT \u2014 Direct threats against specific people or locations, incitement to commit specific acts of mass violence.
|
|
3855
|
+
Examples that FAIL:
|
|
3856
|
+
- "I'm going to find [person] and make them pay. They won't see it coming."
|
|
3857
|
+
- "Someone should shoot up [location]. That would teach them."
|
|
3858
|
+
Examples that PASS:
|
|
3859
|
+
- Instructions or information about weapons (legally protected speech without incitement)
|
|
3860
|
+
- Historical/philosophical discussion of violence
|
|
3861
|
+
- Fictional violence (games, stories, D&D campaigns)
|
|
3862
|
+
- Colloquial expressions ("I could kill for a burger")
|
|
3863
|
+
|
|
3864
|
+
3. CSAM \u2014 Any sexual content involving minors. Zero tolerance.
|
|
3865
|
+
|
|
3866
|
+
4. SELF-HARM ENCOURAGEMENT \u2014 Detailed instructions or active encouragement for self-harm or suicide.
|
|
3867
|
+
Examples that PASS:
|
|
3868
|
+
- Discussions about depression or mental health struggles
|
|
3869
|
+
- Jokes about suicide or dark humor about self-harm
|
|
3870
|
+
- Journaling about difficult feelings
|
|
3871
|
+
|
|
3872
|
+
ALWAYS ALLOW:
|
|
3873
|
+
- Harsh political opinions, criticism of governments or public figures
|
|
3874
|
+
- Edgy humor, dark comedy, offensive jokes (without targeted dehumanization)
|
|
3875
|
+
- Strong opinions about religion, ideology, or social issues
|
|
3876
|
+
- Profanity and vulgar language
|
|
3877
|
+
- Controversial or uncomfortable topics
|
|
3878
|
+
- Educational/historical content about atrocities
|
|
3879
|
+
- The French Revolution, violent revolution as philosophical concept
|
|
3880
|
+
|
|
3881
|
+
Content to evaluate:
|
|
3882
|
+
---
|
|
3883
|
+
${content}
|
|
3884
|
+
---
|
|
3885
|
+
|
|
3886
|
+
Respond with ONLY valid JSON:
|
|
3887
|
+
{"pass":true}
|
|
3888
|
+
OR
|
|
3889
|
+
{"pass":false,"reason":"<specific, human-friendly explanation of why this was blocked>","category":"<hate_speech|extremism|violence_incitement|csam|self_harm_encouragement>"}`;
|
|
3890
|
+
}
|
|
3891
|
+
var DEFAULT_CACHE_MAX = 1e3;
|
|
3892
|
+
function hashContent(content) {
|
|
3893
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
3894
|
+
}
|
|
3895
|
+
function createModerationClient(options) {
|
|
3896
|
+
const model = options.model ?? "claude-haiku-4-5-20251001";
|
|
3897
|
+
const cacheMax = options.cacheMax ?? DEFAULT_CACHE_MAX;
|
|
3898
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3899
|
+
return {
|
|
3900
|
+
async moderate(content) {
|
|
3901
|
+
const hash = hashContent(content);
|
|
3902
|
+
const cached = cache.get(hash);
|
|
3903
|
+
if (cached)
|
|
3904
|
+
return cached;
|
|
3905
|
+
try {
|
|
3906
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
3907
|
+
method: "POST",
|
|
3908
|
+
headers: {
|
|
3909
|
+
"Content-Type": "application/json",
|
|
3910
|
+
"x-api-key": options.apiKey,
|
|
3911
|
+
"anthropic-version": "2023-06-01"
|
|
3912
|
+
},
|
|
3913
|
+
body: JSON.stringify({
|
|
3914
|
+
model,
|
|
3915
|
+
max_tokens: 256,
|
|
3916
|
+
messages: [{ role: "user", content: buildModerationPrompt(content) }]
|
|
3917
|
+
})
|
|
3918
|
+
});
|
|
3919
|
+
if (!response.ok) {
|
|
3920
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
3921
|
+
}
|
|
3922
|
+
const data = await response.json();
|
|
3923
|
+
const text = data.content?.[0]?.text ?? "";
|
|
3924
|
+
const parsed = JSON.parse(text);
|
|
3925
|
+
const result = {
|
|
3926
|
+
pass: parsed.pass === true,
|
|
3927
|
+
reason: parsed.reason ?? "",
|
|
3928
|
+
category: parsed.pass ? void 0 : parsed.category
|
|
3929
|
+
};
|
|
3930
|
+
if (cache.size >= cacheMax) {
|
|
3931
|
+
const oldest = cache.keys().next().value;
|
|
3932
|
+
cache.delete(oldest);
|
|
3933
|
+
}
|
|
3934
|
+
cache.set(hash, result);
|
|
3935
|
+
return result;
|
|
3936
|
+
} catch {
|
|
3937
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3725
3943
|
// src/weaviate/schema.ts
|
|
3726
3944
|
init_logger();
|
|
3727
3945
|
|
|
@@ -3838,6 +4056,7 @@ function getMemoryCollection(userId) {
|
|
|
3838
4056
|
var coreLogger = createLogger("info");
|
|
3839
4057
|
var tokenService = new ConfirmationTokenService(coreLogger);
|
|
3840
4058
|
var preferencesService = new PreferencesDatabaseService(coreLogger);
|
|
4059
|
+
var moderationClient = process.env.ANTHROPIC_API_KEY ? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY }) : void 0;
|
|
3841
4060
|
var coreServicesCache = /* @__PURE__ */ new Map();
|
|
3842
4061
|
function createCoreServices(userId) {
|
|
3843
4062
|
const cached = coreServicesCache.get(userId);
|
|
@@ -3848,7 +4067,7 @@ function createCoreServices(userId) {
|
|
|
3848
4067
|
const services = {
|
|
3849
4068
|
memory: new MemoryService(collection, userId, coreLogger),
|
|
3850
4069
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
3851
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger),
|
|
4070
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, { moderationClient }),
|
|
3852
4071
|
preferences: preferencesService,
|
|
3853
4072
|
token: tokenService
|
|
3854
4073
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/remember-mcp",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.21",
|
|
4
4
|
"description": "Multi-tenant memory system MCP server with vector search and relationships",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
51
51
|
"@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
|
|
52
52
|
"@prmichaelsen/mcp-auth": "^7.0.4",
|
|
53
|
-
"@prmichaelsen/remember-core": "^0.
|
|
53
|
+
"@prmichaelsen/remember-core": "^0.32.1",
|
|
54
54
|
"dotenv": "^16.4.5",
|
|
55
55
|
"uuid": "^13.0.0",
|
|
56
56
|
"weaviate-client": "^3.2.0"
|
package/src/core-services.ts
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
PreferencesDatabaseService,
|
|
13
13
|
ConfirmationTokenService,
|
|
14
14
|
createLogger,
|
|
15
|
+
createModerationClient,
|
|
15
16
|
} from '@prmichaelsen/remember-core';
|
|
16
|
-
import type { Logger } from '@prmichaelsen/remember-core';
|
|
17
|
+
import type { Logger, ModerationClient } from '@prmichaelsen/remember-core';
|
|
17
18
|
import { getWeaviateClient } from './weaviate/client.js';
|
|
18
19
|
import { getMemoryCollection } from './weaviate/schema.js';
|
|
19
20
|
|
|
@@ -29,6 +30,9 @@ export interface CoreServices {
|
|
|
29
30
|
const coreLogger: Logger = createLogger('info');
|
|
30
31
|
const tokenService = new ConfirmationTokenService(coreLogger);
|
|
31
32
|
const preferencesService = new PreferencesDatabaseService(coreLogger);
|
|
33
|
+
const moderationClient: ModerationClient | undefined = process.env.ANTHROPIC_API_KEY
|
|
34
|
+
? createModerationClient({ apiKey: process.env.ANTHROPIC_API_KEY })
|
|
35
|
+
: undefined;
|
|
32
36
|
|
|
33
37
|
/** Cached CoreServices per userId — avoids re-instantiation on every tool call */
|
|
34
38
|
const coreServicesCache = new Map<string, CoreServices>();
|
|
@@ -47,7 +51,7 @@ export function createCoreServices(userId: string): CoreServices {
|
|
|
47
51
|
const services: CoreServices = {
|
|
48
52
|
memory: new MemoryService(collection, userId, coreLogger),
|
|
49
53
|
relationship: new RelationshipService(collection, userId, coreLogger),
|
|
50
|
-
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger),
|
|
54
|
+
space: new SpaceService(weaviateClient, collection, userId, tokenService, coreLogger, { moderationClient }),
|
|
51
55
|
preferences: preferencesService,
|
|
52
56
|
token: tokenService,
|
|
53
57
|
};
|