@prmichaelsen/remember-mcp 3.15.3 → 3.15.4
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/dist/server-factory.js +248 -12
- package/dist/server.js +248 -12
- package/package.json +2 -2
package/dist/server-factory.js
CHANGED
|
@@ -2331,6 +2331,38 @@ async function getSpaceConfig(id, type, logger2) {
|
|
|
2331
2331
|
// node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
|
|
2332
2332
|
import { Filters as Filters2 } from "weaviate-client";
|
|
2333
2333
|
|
|
2334
|
+
// node_modules/@prmichaelsen/remember-core/dist/services/discovery.js
|
|
2335
|
+
var DISCOVERY_RATIO = 4;
|
|
2336
|
+
var DISCOVERY_THRESHOLD = 5;
|
|
2337
|
+
function interleaveDiscovery(options) {
|
|
2338
|
+
const { rated, discovery, ratio = DISCOVERY_RATIO, offset = 0, limit } = options;
|
|
2339
|
+
const totalAvailable = rated.length + discovery.length;
|
|
2340
|
+
if (totalAvailable === 0)
|
|
2341
|
+
return [];
|
|
2342
|
+
const merged = [];
|
|
2343
|
+
let ratedIdx = 0;
|
|
2344
|
+
let discoveryIdx = 0;
|
|
2345
|
+
for (let pos = 0; pos < totalAvailable; pos++) {
|
|
2346
|
+
const isDiscoverySlot = (pos + 1) % (ratio + 1) === 0;
|
|
2347
|
+
if (isDiscoverySlot) {
|
|
2348
|
+
if (discoveryIdx < discovery.length) {
|
|
2349
|
+
merged.push({ item: discovery[discoveryIdx++], is_discovery: true });
|
|
2350
|
+
} else if (ratedIdx < rated.length) {
|
|
2351
|
+
merged.push({ item: rated[ratedIdx++], is_discovery: false });
|
|
2352
|
+
}
|
|
2353
|
+
} else {
|
|
2354
|
+
if (ratedIdx < rated.length) {
|
|
2355
|
+
merged.push({ item: rated[ratedIdx++], is_discovery: false });
|
|
2356
|
+
} else if (discoveryIdx < discovery.length) {
|
|
2357
|
+
merged.push({ item: discovery[discoveryIdx++], is_discovery: true });
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
const start = Math.min(offset, merged.length);
|
|
2362
|
+
const end = limit !== void 0 ? Math.min(start + limit, merged.length) : merged.length;
|
|
2363
|
+
return merged.slice(start, end);
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2334
2366
|
// node_modules/@prmichaelsen/remember-core/dist/database/weaviate/client.js
|
|
2335
2367
|
import weaviate2 from "weaviate-client";
|
|
2336
2368
|
var ALL_MEMORY_PROPERTIES = [
|
|
@@ -2711,6 +2743,72 @@ var MemoryService = class {
|
|
|
2711
2743
|
limit
|
|
2712
2744
|
};
|
|
2713
2745
|
}
|
|
2746
|
+
// ── By Discovery (interleaved rated + unrated) ────────────────────
|
|
2747
|
+
async byDiscovery(input) {
|
|
2748
|
+
const limit = input.limit ?? 50;
|
|
2749
|
+
const offset = input.offset ?? 0;
|
|
2750
|
+
const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
|
|
2751
|
+
const ghostFilters = [];
|
|
2752
|
+
if (input.ghost_context) {
|
|
2753
|
+
ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
|
|
2754
|
+
}
|
|
2755
|
+
if (!input.ghost_context?.include_ghost_content) {
|
|
2756
|
+
ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
|
|
2757
|
+
}
|
|
2758
|
+
const buildBaseFilters = (useDeletedFilter) => {
|
|
2759
|
+
const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
|
|
2760
|
+
return [deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null);
|
|
2761
|
+
};
|
|
2762
|
+
const fetchLimit = (limit + offset) * 2;
|
|
2763
|
+
const executeRated = async (useDeletedFilter) => {
|
|
2764
|
+
const base = buildBaseFilters(useDeletedFilter);
|
|
2765
|
+
base.push(this.collection.filter.byProperty("rating_count").greaterOrEqual(RATING_MIN_THRESHOLD));
|
|
2766
|
+
const combinedFilters = combineFiltersWithAnd(base);
|
|
2767
|
+
const queryOptions = {
|
|
2768
|
+
limit: fetchLimit,
|
|
2769
|
+
sort: this.collection.sort.byProperty("rating_bayesian", false)
|
|
2770
|
+
};
|
|
2771
|
+
if (combinedFilters)
|
|
2772
|
+
queryOptions.filters = combinedFilters;
|
|
2773
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2774
|
+
};
|
|
2775
|
+
const executeDiscovery = async (useDeletedFilter) => {
|
|
2776
|
+
const base = buildBaseFilters(useDeletedFilter);
|
|
2777
|
+
base.push(this.collection.filter.byProperty("rating_count").lessThan(RATING_MIN_THRESHOLD));
|
|
2778
|
+
const combinedFilters = combineFiltersWithAnd(base);
|
|
2779
|
+
const queryOptions = {
|
|
2780
|
+
limit: fetchLimit,
|
|
2781
|
+
sort: this.collection.sort.byProperty("created_at", false)
|
|
2782
|
+
};
|
|
2783
|
+
if (combinedFilters)
|
|
2784
|
+
queryOptions.filters = combinedFilters;
|
|
2785
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2786
|
+
};
|
|
2787
|
+
const [ratedResults, discoveryResults] = await Promise.all([
|
|
2788
|
+
this.retryWithoutDeletedFilter(executeRated),
|
|
2789
|
+
this.retryWithoutDeletedFilter(executeDiscovery)
|
|
2790
|
+
]);
|
|
2791
|
+
const toDoc = (obj) => normalizeDoc({ id: obj.uuid, ...obj.properties });
|
|
2792
|
+
const ratedDocs = ratedResults.objects.map(toDoc).filter((d) => d.doc_type === "memory");
|
|
2793
|
+
const discoveryDocs = discoveryResults.objects.map(toDoc).filter((d) => d.doc_type === "memory");
|
|
2794
|
+
const interleaved = interleaveDiscovery({
|
|
2795
|
+
rated: ratedDocs,
|
|
2796
|
+
discovery: discoveryDocs,
|
|
2797
|
+
ratio: DISCOVERY_RATIO,
|
|
2798
|
+
offset,
|
|
2799
|
+
limit
|
|
2800
|
+
});
|
|
2801
|
+
const memories = interleaved.map((item) => {
|
|
2802
|
+
const doc = item.item;
|
|
2803
|
+
return Object.assign({}, doc, { is_discovery: item.is_discovery });
|
|
2804
|
+
});
|
|
2805
|
+
return {
|
|
2806
|
+
memories,
|
|
2807
|
+
total: memories.length,
|
|
2808
|
+
offset,
|
|
2809
|
+
limit
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2714
2812
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2715
2813
|
async findSimilar(input) {
|
|
2716
2814
|
if (!input.memory_id && !input.text)
|
|
@@ -3908,6 +4006,135 @@ var SpaceService = class {
|
|
|
3908
4006
|
results
|
|
3909
4007
|
};
|
|
3910
4008
|
}
|
|
4009
|
+
// ── By Discovery (interleaved rated + unrated for spaces/groups) ────
|
|
4010
|
+
async byDiscovery(input, authContext) {
|
|
4011
|
+
const spaces = input.spaces || [];
|
|
4012
|
+
const groups = input.groups || [];
|
|
4013
|
+
const limit = input.limit ?? 10;
|
|
4014
|
+
const offset = input.offset ?? 0;
|
|
4015
|
+
if (spaces.length > 0) {
|
|
4016
|
+
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
4017
|
+
if (invalidSpaces.length > 0) {
|
|
4018
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
if (groups.length > 0) {
|
|
4022
|
+
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
4023
|
+
if (invalidGroups.length > 0) {
|
|
4024
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
const moderationFilter = input.moderation_filter || "approved";
|
|
4028
|
+
if (moderationFilter !== "approved") {
|
|
4029
|
+
for (const groupId of groups) {
|
|
4030
|
+
if (!canModerate(authContext, groupId)) {
|
|
4031
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in group ${groupId}`);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
if ((spaces.length > 0 || groups.length === 0) && !canModerateAny(authContext)) {
|
|
4035
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in spaces`);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
const fetchLimit = (limit + offset) * 2;
|
|
4039
|
+
const fetchPool = async (collection, baseFilters, ratingFilter, sortProp) => {
|
|
4040
|
+
const allFilters = [...baseFilters, ratingFilter];
|
|
4041
|
+
const combined = allFilters.length > 0 ? Filters4.and(...allFilters) : void 0;
|
|
4042
|
+
const queryOptions = {
|
|
4043
|
+
limit: fetchLimit,
|
|
4044
|
+
sort: collection.sort.byProperty(sortProp, false)
|
|
4045
|
+
};
|
|
4046
|
+
if (combined)
|
|
4047
|
+
queryOptions.filters = combined;
|
|
4048
|
+
return (await collection.query.fetchObjects(queryOptions)).objects;
|
|
4049
|
+
};
|
|
4050
|
+
const searchInput = {
|
|
4051
|
+
query: "",
|
|
4052
|
+
// not used for filter building
|
|
4053
|
+
spaces: input.spaces,
|
|
4054
|
+
groups: input.groups,
|
|
4055
|
+
content_type: input.content_type,
|
|
4056
|
+
tags: input.tags,
|
|
4057
|
+
min_weight: input.min_weight,
|
|
4058
|
+
max_weight: input.max_weight,
|
|
4059
|
+
date_from: input.date_from,
|
|
4060
|
+
date_to: input.date_to,
|
|
4061
|
+
moderation_filter: input.moderation_filter,
|
|
4062
|
+
include_comments: input.include_comments
|
|
4063
|
+
};
|
|
4064
|
+
const allRated = [];
|
|
4065
|
+
const allDiscovery = [];
|
|
4066
|
+
if (spaces.length > 0 || groups.length === 0) {
|
|
4067
|
+
await ensurePublicCollection(this.weaviateClient);
|
|
4068
|
+
const spacesCollectionName = getCollectionName(CollectionType.SPACES);
|
|
4069
|
+
const spacesCollection = this.weaviateClient.collections.get(spacesCollectionName);
|
|
4070
|
+
const baseFilters = this.buildBaseFilters(spacesCollection, searchInput);
|
|
4071
|
+
if (spaces.length > 0) {
|
|
4072
|
+
baseFilters.push(spacesCollection.filter.byProperty("space_ids").containsAny(spaces));
|
|
4073
|
+
}
|
|
4074
|
+
const ratedFilter = spacesCollection.filter.byProperty("rating_count").greaterOrEqual(DISCOVERY_THRESHOLD);
|
|
4075
|
+
const discoveryFilter = spacesCollection.filter.byProperty("rating_count").lessThan(DISCOVERY_THRESHOLD);
|
|
4076
|
+
const [rated, discovery] = await Promise.all([
|
|
4077
|
+
fetchPool(spacesCollection, baseFilters, ratedFilter, "rating_bayesian"),
|
|
4078
|
+
fetchPool(spacesCollection, baseFilters, discoveryFilter, "created_at")
|
|
4079
|
+
]);
|
|
4080
|
+
allRated.push(...tagWithSource(rated, spacesCollectionName));
|
|
4081
|
+
allDiscovery.push(...tagWithSource(discovery, spacesCollectionName));
|
|
4082
|
+
}
|
|
4083
|
+
for (const groupId of groups) {
|
|
4084
|
+
const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
|
|
4085
|
+
const exists = await this.weaviateClient.collections.exists(groupCollectionName);
|
|
4086
|
+
if (!exists)
|
|
4087
|
+
continue;
|
|
4088
|
+
const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
|
|
4089
|
+
const baseFilters = this.buildBaseFilters(groupCollection, searchInput);
|
|
4090
|
+
const ratedFilter = groupCollection.filter.byProperty("rating_count").greaterOrEqual(DISCOVERY_THRESHOLD);
|
|
4091
|
+
const discoveryFilter = groupCollection.filter.byProperty("rating_count").lessThan(DISCOVERY_THRESHOLD);
|
|
4092
|
+
const [rated, discovery] = await Promise.all([
|
|
4093
|
+
fetchPool(groupCollection, baseFilters, ratedFilter, "rating_bayesian"),
|
|
4094
|
+
fetchPool(groupCollection, baseFilters, discoveryFilter, "created_at")
|
|
4095
|
+
]);
|
|
4096
|
+
allRated.push(...tagWithSource(rated, groupCollectionName));
|
|
4097
|
+
allDiscovery.push(...tagWithSource(discovery, groupCollectionName));
|
|
4098
|
+
}
|
|
4099
|
+
const dedupePool = (pool) => {
|
|
4100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4101
|
+
return pool.filter((obj) => {
|
|
4102
|
+
if (seen.has(obj.uuid))
|
|
4103
|
+
return false;
|
|
4104
|
+
seen.add(obj.uuid);
|
|
4105
|
+
return true;
|
|
4106
|
+
});
|
|
4107
|
+
};
|
|
4108
|
+
const ratedDeduped = dedupeBySourceId(dedupePool(allRated), input.dedupe);
|
|
4109
|
+
const discoveryDeduped = dedupeBySourceId(dedupePool(allDiscovery), input.dedupe);
|
|
4110
|
+
const toDoc = (obj) => ({
|
|
4111
|
+
id: obj.uuid,
|
|
4112
|
+
...obj.properties,
|
|
4113
|
+
...obj._also_in?.length ? { also_in: obj._also_in } : {}
|
|
4114
|
+
});
|
|
4115
|
+
const ratedDocs = ratedDeduped.map(toDoc);
|
|
4116
|
+
const discoveryDocs = discoveryDeduped.map(toDoc);
|
|
4117
|
+
const interleaved = interleaveDiscovery({
|
|
4118
|
+
rated: ratedDocs,
|
|
4119
|
+
discovery: discoveryDocs,
|
|
4120
|
+
ratio: DISCOVERY_RATIO,
|
|
4121
|
+
offset,
|
|
4122
|
+
limit
|
|
4123
|
+
});
|
|
4124
|
+
const memories = interleaved.map((item) => {
|
|
4125
|
+
const doc = item.item;
|
|
4126
|
+
return Object.assign({}, doc, { is_discovery: item.is_discovery });
|
|
4127
|
+
});
|
|
4128
|
+
const isAllPublic = spaces.length === 0 && groups.length === 0;
|
|
4129
|
+
return {
|
|
4130
|
+
spaces_searched: isAllPublic ? "all_public" : spaces,
|
|
4131
|
+
groups_searched: groups,
|
|
4132
|
+
memories,
|
|
4133
|
+
total: memories.length,
|
|
4134
|
+
offset,
|
|
4135
|
+
limit
|
|
4136
|
+
};
|
|
4137
|
+
}
|
|
3911
4138
|
// ── Private: Build Base Filters ─────────────────────────────────────
|
|
3912
4139
|
buildBaseFilters(collection, input) {
|
|
3913
4140
|
const filterList = [];
|
|
@@ -3945,6 +4172,10 @@ var SpaceService = class {
|
|
|
3945
4172
|
const opts = { limit };
|
|
3946
4173
|
if (filters)
|
|
3947
4174
|
opts.filters = filters;
|
|
4175
|
+
const isWildcard = !query.trim() || query === "*";
|
|
4176
|
+
if (isWildcard) {
|
|
4177
|
+
return (await collection.query.bm25("*", opts)).objects;
|
|
4178
|
+
}
|
|
3948
4179
|
switch (searchType) {
|
|
3949
4180
|
case "bm25":
|
|
3950
4181
|
return (await collection.query.bm25(query, opts)).objects;
|
|
@@ -4047,10 +4278,13 @@ Content to evaluate:
|
|
|
4047
4278
|
${content}
|
|
4048
4279
|
---
|
|
4049
4280
|
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
{"pass":
|
|
4281
|
+
\u{1F6A8} CRITICAL \u{1F6A8}:
|
|
4282
|
+
Respond with ONLY a single JSON object on one line \u2014 no markdown, no explanation, no code fences.
|
|
4283
|
+
|
|
4284
|
+
If the content passes: {"pass":true,"reason":"<brief reason>"}
|
|
4285
|
+
If the content fails: {"pass":false,"reason":"<specific explanation>","category":"<hate_speech|extremism|violence_incitement|csam|self_harm_encouragement>"}
|
|
4286
|
+
|
|
4287
|
+
Your entire response must be parseable by JSON.parse(). Do not include any other text.`;
|
|
4054
4288
|
}
|
|
4055
4289
|
var DEFAULT_CACHE_MAX = 1e3;
|
|
4056
4290
|
function hashContent(content) {
|
|
@@ -4077,18 +4311,21 @@ function createModerationClient(options) {
|
|
|
4077
4311
|
body: JSON.stringify({
|
|
4078
4312
|
model,
|
|
4079
4313
|
max_tokens: 256,
|
|
4080
|
-
|
|
4314
|
+
temperature: 0,
|
|
4315
|
+
messages: [
|
|
4316
|
+
{ role: "user", content: buildModerationPrompt(content) },
|
|
4317
|
+
{ role: "assistant", content: "{" }
|
|
4318
|
+
]
|
|
4081
4319
|
})
|
|
4082
4320
|
});
|
|
4083
4321
|
if (!response.ok) {
|
|
4084
4322
|
const errorBody = await response.text().catch(() => "");
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
return { pass: false, reason: msg };
|
|
4323
|
+
console.error(`[moderation] Anthropic API error: ${response.status} ${response.statusText}`, errorBody);
|
|
4324
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4088
4325
|
}
|
|
4089
4326
|
const data = await response.json();
|
|
4090
4327
|
const rawText = data.content?.[0]?.text ?? "";
|
|
4091
|
-
const text = rawText.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4328
|
+
const text = ("{" + rawText).replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4092
4329
|
const parsed = JSON.parse(text);
|
|
4093
4330
|
const result = {
|
|
4094
4331
|
pass: parsed.pass === true,
|
|
@@ -4102,9 +4339,8 @@ function createModerationClient(options) {
|
|
|
4102
4339
|
cache.set(hash, result);
|
|
4103
4340
|
return result;
|
|
4104
4341
|
} catch (err2) {
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
return { pass: false, reason: msg };
|
|
4342
|
+
console.error("[moderation] Unexpected error:", err2);
|
|
4343
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4108
4344
|
}
|
|
4109
4345
|
}
|
|
4110
4346
|
};
|
package/dist/server.js
CHANGED
|
@@ -2335,6 +2335,38 @@ async function getSpaceConfig(id, type, logger2) {
|
|
|
2335
2335
|
// node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
|
|
2336
2336
|
import { Filters as Filters2 } from "weaviate-client";
|
|
2337
2337
|
|
|
2338
|
+
// node_modules/@prmichaelsen/remember-core/dist/services/discovery.js
|
|
2339
|
+
var DISCOVERY_RATIO = 4;
|
|
2340
|
+
var DISCOVERY_THRESHOLD = 5;
|
|
2341
|
+
function interleaveDiscovery(options) {
|
|
2342
|
+
const { rated, discovery, ratio = DISCOVERY_RATIO, offset = 0, limit } = options;
|
|
2343
|
+
const totalAvailable = rated.length + discovery.length;
|
|
2344
|
+
if (totalAvailable === 0)
|
|
2345
|
+
return [];
|
|
2346
|
+
const merged = [];
|
|
2347
|
+
let ratedIdx = 0;
|
|
2348
|
+
let discoveryIdx = 0;
|
|
2349
|
+
for (let pos = 0; pos < totalAvailable; pos++) {
|
|
2350
|
+
const isDiscoverySlot = (pos + 1) % (ratio + 1) === 0;
|
|
2351
|
+
if (isDiscoverySlot) {
|
|
2352
|
+
if (discoveryIdx < discovery.length) {
|
|
2353
|
+
merged.push({ item: discovery[discoveryIdx++], is_discovery: true });
|
|
2354
|
+
} else if (ratedIdx < rated.length) {
|
|
2355
|
+
merged.push({ item: rated[ratedIdx++], is_discovery: false });
|
|
2356
|
+
}
|
|
2357
|
+
} else {
|
|
2358
|
+
if (ratedIdx < rated.length) {
|
|
2359
|
+
merged.push({ item: rated[ratedIdx++], is_discovery: false });
|
|
2360
|
+
} else if (discoveryIdx < discovery.length) {
|
|
2361
|
+
merged.push({ item: discovery[discoveryIdx++], is_discovery: true });
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
const start = Math.min(offset, merged.length);
|
|
2366
|
+
const end = limit !== void 0 ? Math.min(start + limit, merged.length) : merged.length;
|
|
2367
|
+
return merged.slice(start, end);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2338
2370
|
// node_modules/@prmichaelsen/remember-core/dist/database/weaviate/client.js
|
|
2339
2371
|
import weaviate2 from "weaviate-client";
|
|
2340
2372
|
var ALL_MEMORY_PROPERTIES = [
|
|
@@ -2715,6 +2747,72 @@ var MemoryService = class {
|
|
|
2715
2747
|
limit
|
|
2716
2748
|
};
|
|
2717
2749
|
}
|
|
2750
|
+
// ── By Discovery (interleaved rated + unrated) ────────────────────
|
|
2751
|
+
async byDiscovery(input) {
|
|
2752
|
+
const limit = input.limit ?? 50;
|
|
2753
|
+
const offset = input.offset ?? 0;
|
|
2754
|
+
const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
|
|
2755
|
+
const ghostFilters = [];
|
|
2756
|
+
if (input.ghost_context) {
|
|
2757
|
+
ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
|
|
2758
|
+
}
|
|
2759
|
+
if (!input.ghost_context?.include_ghost_content) {
|
|
2760
|
+
ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
|
|
2761
|
+
}
|
|
2762
|
+
const buildBaseFilters = (useDeletedFilter) => {
|
|
2763
|
+
const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
|
|
2764
|
+
return [deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null);
|
|
2765
|
+
};
|
|
2766
|
+
const fetchLimit = (limit + offset) * 2;
|
|
2767
|
+
const executeRated = async (useDeletedFilter) => {
|
|
2768
|
+
const base = buildBaseFilters(useDeletedFilter);
|
|
2769
|
+
base.push(this.collection.filter.byProperty("rating_count").greaterOrEqual(RATING_MIN_THRESHOLD));
|
|
2770
|
+
const combinedFilters = combineFiltersWithAnd(base);
|
|
2771
|
+
const queryOptions = {
|
|
2772
|
+
limit: fetchLimit,
|
|
2773
|
+
sort: this.collection.sort.byProperty("rating_bayesian", false)
|
|
2774
|
+
};
|
|
2775
|
+
if (combinedFilters)
|
|
2776
|
+
queryOptions.filters = combinedFilters;
|
|
2777
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2778
|
+
};
|
|
2779
|
+
const executeDiscovery = async (useDeletedFilter) => {
|
|
2780
|
+
const base = buildBaseFilters(useDeletedFilter);
|
|
2781
|
+
base.push(this.collection.filter.byProperty("rating_count").lessThan(RATING_MIN_THRESHOLD));
|
|
2782
|
+
const combinedFilters = combineFiltersWithAnd(base);
|
|
2783
|
+
const queryOptions = {
|
|
2784
|
+
limit: fetchLimit,
|
|
2785
|
+
sort: this.collection.sort.byProperty("created_at", false)
|
|
2786
|
+
};
|
|
2787
|
+
if (combinedFilters)
|
|
2788
|
+
queryOptions.filters = combinedFilters;
|
|
2789
|
+
return this.collection.query.fetchObjects(queryOptions);
|
|
2790
|
+
};
|
|
2791
|
+
const [ratedResults, discoveryResults] = await Promise.all([
|
|
2792
|
+
this.retryWithoutDeletedFilter(executeRated),
|
|
2793
|
+
this.retryWithoutDeletedFilter(executeDiscovery)
|
|
2794
|
+
]);
|
|
2795
|
+
const toDoc = (obj) => normalizeDoc({ id: obj.uuid, ...obj.properties });
|
|
2796
|
+
const ratedDocs = ratedResults.objects.map(toDoc).filter((d) => d.doc_type === "memory");
|
|
2797
|
+
const discoveryDocs = discoveryResults.objects.map(toDoc).filter((d) => d.doc_type === "memory");
|
|
2798
|
+
const interleaved = interleaveDiscovery({
|
|
2799
|
+
rated: ratedDocs,
|
|
2800
|
+
discovery: discoveryDocs,
|
|
2801
|
+
ratio: DISCOVERY_RATIO,
|
|
2802
|
+
offset,
|
|
2803
|
+
limit
|
|
2804
|
+
});
|
|
2805
|
+
const memories = interleaved.map((item) => {
|
|
2806
|
+
const doc = item.item;
|
|
2807
|
+
return Object.assign({}, doc, { is_discovery: item.is_discovery });
|
|
2808
|
+
});
|
|
2809
|
+
return {
|
|
2810
|
+
memories,
|
|
2811
|
+
total: memories.length,
|
|
2812
|
+
offset,
|
|
2813
|
+
limit
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2718
2816
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2719
2817
|
async findSimilar(input) {
|
|
2720
2818
|
if (!input.memory_id && !input.text)
|
|
@@ -3912,6 +4010,135 @@ var SpaceService = class {
|
|
|
3912
4010
|
results
|
|
3913
4011
|
};
|
|
3914
4012
|
}
|
|
4013
|
+
// ── By Discovery (interleaved rated + unrated for spaces/groups) ────
|
|
4014
|
+
async byDiscovery(input, authContext) {
|
|
4015
|
+
const spaces = input.spaces || [];
|
|
4016
|
+
const groups = input.groups || [];
|
|
4017
|
+
const limit = input.limit ?? 10;
|
|
4018
|
+
const offset = input.offset ?? 0;
|
|
4019
|
+
if (spaces.length > 0) {
|
|
4020
|
+
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
4021
|
+
if (invalidSpaces.length > 0) {
|
|
4022
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
if (groups.length > 0) {
|
|
4026
|
+
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
4027
|
+
if (invalidGroups.length > 0) {
|
|
4028
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
const moderationFilter = input.moderation_filter || "approved";
|
|
4032
|
+
if (moderationFilter !== "approved") {
|
|
4033
|
+
for (const groupId of groups) {
|
|
4034
|
+
if (!canModerate(authContext, groupId)) {
|
|
4035
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in group ${groupId}`);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
if ((spaces.length > 0 || groups.length === 0) && !canModerateAny(authContext)) {
|
|
4039
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in spaces`);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
const fetchLimit = (limit + offset) * 2;
|
|
4043
|
+
const fetchPool = async (collection, baseFilters, ratingFilter, sortProp) => {
|
|
4044
|
+
const allFilters = [...baseFilters, ratingFilter];
|
|
4045
|
+
const combined = allFilters.length > 0 ? Filters4.and(...allFilters) : void 0;
|
|
4046
|
+
const queryOptions = {
|
|
4047
|
+
limit: fetchLimit,
|
|
4048
|
+
sort: collection.sort.byProperty(sortProp, false)
|
|
4049
|
+
};
|
|
4050
|
+
if (combined)
|
|
4051
|
+
queryOptions.filters = combined;
|
|
4052
|
+
return (await collection.query.fetchObjects(queryOptions)).objects;
|
|
4053
|
+
};
|
|
4054
|
+
const searchInput = {
|
|
4055
|
+
query: "",
|
|
4056
|
+
// not used for filter building
|
|
4057
|
+
spaces: input.spaces,
|
|
4058
|
+
groups: input.groups,
|
|
4059
|
+
content_type: input.content_type,
|
|
4060
|
+
tags: input.tags,
|
|
4061
|
+
min_weight: input.min_weight,
|
|
4062
|
+
max_weight: input.max_weight,
|
|
4063
|
+
date_from: input.date_from,
|
|
4064
|
+
date_to: input.date_to,
|
|
4065
|
+
moderation_filter: input.moderation_filter,
|
|
4066
|
+
include_comments: input.include_comments
|
|
4067
|
+
};
|
|
4068
|
+
const allRated = [];
|
|
4069
|
+
const allDiscovery = [];
|
|
4070
|
+
if (spaces.length > 0 || groups.length === 0) {
|
|
4071
|
+
await ensurePublicCollection(this.weaviateClient);
|
|
4072
|
+
const spacesCollectionName = getCollectionName(CollectionType.SPACES);
|
|
4073
|
+
const spacesCollection = this.weaviateClient.collections.get(spacesCollectionName);
|
|
4074
|
+
const baseFilters = this.buildBaseFilters(spacesCollection, searchInput);
|
|
4075
|
+
if (spaces.length > 0) {
|
|
4076
|
+
baseFilters.push(spacesCollection.filter.byProperty("space_ids").containsAny(spaces));
|
|
4077
|
+
}
|
|
4078
|
+
const ratedFilter = spacesCollection.filter.byProperty("rating_count").greaterOrEqual(DISCOVERY_THRESHOLD);
|
|
4079
|
+
const discoveryFilter = spacesCollection.filter.byProperty("rating_count").lessThan(DISCOVERY_THRESHOLD);
|
|
4080
|
+
const [rated, discovery] = await Promise.all([
|
|
4081
|
+
fetchPool(spacesCollection, baseFilters, ratedFilter, "rating_bayesian"),
|
|
4082
|
+
fetchPool(spacesCollection, baseFilters, discoveryFilter, "created_at")
|
|
4083
|
+
]);
|
|
4084
|
+
allRated.push(...tagWithSource(rated, spacesCollectionName));
|
|
4085
|
+
allDiscovery.push(...tagWithSource(discovery, spacesCollectionName));
|
|
4086
|
+
}
|
|
4087
|
+
for (const groupId of groups) {
|
|
4088
|
+
const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
|
|
4089
|
+
const exists = await this.weaviateClient.collections.exists(groupCollectionName);
|
|
4090
|
+
if (!exists)
|
|
4091
|
+
continue;
|
|
4092
|
+
const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
|
|
4093
|
+
const baseFilters = this.buildBaseFilters(groupCollection, searchInput);
|
|
4094
|
+
const ratedFilter = groupCollection.filter.byProperty("rating_count").greaterOrEqual(DISCOVERY_THRESHOLD);
|
|
4095
|
+
const discoveryFilter = groupCollection.filter.byProperty("rating_count").lessThan(DISCOVERY_THRESHOLD);
|
|
4096
|
+
const [rated, discovery] = await Promise.all([
|
|
4097
|
+
fetchPool(groupCollection, baseFilters, ratedFilter, "rating_bayesian"),
|
|
4098
|
+
fetchPool(groupCollection, baseFilters, discoveryFilter, "created_at")
|
|
4099
|
+
]);
|
|
4100
|
+
allRated.push(...tagWithSource(rated, groupCollectionName));
|
|
4101
|
+
allDiscovery.push(...tagWithSource(discovery, groupCollectionName));
|
|
4102
|
+
}
|
|
4103
|
+
const dedupePool = (pool) => {
|
|
4104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4105
|
+
return pool.filter((obj) => {
|
|
4106
|
+
if (seen.has(obj.uuid))
|
|
4107
|
+
return false;
|
|
4108
|
+
seen.add(obj.uuid);
|
|
4109
|
+
return true;
|
|
4110
|
+
});
|
|
4111
|
+
};
|
|
4112
|
+
const ratedDeduped = dedupeBySourceId(dedupePool(allRated), input.dedupe);
|
|
4113
|
+
const discoveryDeduped = dedupeBySourceId(dedupePool(allDiscovery), input.dedupe);
|
|
4114
|
+
const toDoc = (obj) => ({
|
|
4115
|
+
id: obj.uuid,
|
|
4116
|
+
...obj.properties,
|
|
4117
|
+
...obj._also_in?.length ? { also_in: obj._also_in } : {}
|
|
4118
|
+
});
|
|
4119
|
+
const ratedDocs = ratedDeduped.map(toDoc);
|
|
4120
|
+
const discoveryDocs = discoveryDeduped.map(toDoc);
|
|
4121
|
+
const interleaved = interleaveDiscovery({
|
|
4122
|
+
rated: ratedDocs,
|
|
4123
|
+
discovery: discoveryDocs,
|
|
4124
|
+
ratio: DISCOVERY_RATIO,
|
|
4125
|
+
offset,
|
|
4126
|
+
limit
|
|
4127
|
+
});
|
|
4128
|
+
const memories = interleaved.map((item) => {
|
|
4129
|
+
const doc = item.item;
|
|
4130
|
+
return Object.assign({}, doc, { is_discovery: item.is_discovery });
|
|
4131
|
+
});
|
|
4132
|
+
const isAllPublic = spaces.length === 0 && groups.length === 0;
|
|
4133
|
+
return {
|
|
4134
|
+
spaces_searched: isAllPublic ? "all_public" : spaces,
|
|
4135
|
+
groups_searched: groups,
|
|
4136
|
+
memories,
|
|
4137
|
+
total: memories.length,
|
|
4138
|
+
offset,
|
|
4139
|
+
limit
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
3915
4142
|
// ── Private: Build Base Filters ─────────────────────────────────────
|
|
3916
4143
|
buildBaseFilters(collection, input) {
|
|
3917
4144
|
const filterList = [];
|
|
@@ -3949,6 +4176,10 @@ var SpaceService = class {
|
|
|
3949
4176
|
const opts = { limit };
|
|
3950
4177
|
if (filters)
|
|
3951
4178
|
opts.filters = filters;
|
|
4179
|
+
const isWildcard = !query.trim() || query === "*";
|
|
4180
|
+
if (isWildcard) {
|
|
4181
|
+
return (await collection.query.bm25("*", opts)).objects;
|
|
4182
|
+
}
|
|
3952
4183
|
switch (searchType) {
|
|
3953
4184
|
case "bm25":
|
|
3954
4185
|
return (await collection.query.bm25(query, opts)).objects;
|
|
@@ -4051,10 +4282,13 @@ Content to evaluate:
|
|
|
4051
4282
|
${content}
|
|
4052
4283
|
---
|
|
4053
4284
|
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
{"pass":
|
|
4285
|
+
\u{1F6A8} CRITICAL \u{1F6A8}:
|
|
4286
|
+
Respond with ONLY a single JSON object on one line \u2014 no markdown, no explanation, no code fences.
|
|
4287
|
+
|
|
4288
|
+
If the content passes: {"pass":true,"reason":"<brief reason>"}
|
|
4289
|
+
If the content fails: {"pass":false,"reason":"<specific explanation>","category":"<hate_speech|extremism|violence_incitement|csam|self_harm_encouragement>"}
|
|
4290
|
+
|
|
4291
|
+
Your entire response must be parseable by JSON.parse(). Do not include any other text.`;
|
|
4058
4292
|
}
|
|
4059
4293
|
var DEFAULT_CACHE_MAX = 1e3;
|
|
4060
4294
|
function hashContent(content) {
|
|
@@ -4081,18 +4315,21 @@ function createModerationClient(options) {
|
|
|
4081
4315
|
body: JSON.stringify({
|
|
4082
4316
|
model,
|
|
4083
4317
|
max_tokens: 256,
|
|
4084
|
-
|
|
4318
|
+
temperature: 0,
|
|
4319
|
+
messages: [
|
|
4320
|
+
{ role: "user", content: buildModerationPrompt(content) },
|
|
4321
|
+
{ role: "assistant", content: "{" }
|
|
4322
|
+
]
|
|
4085
4323
|
})
|
|
4086
4324
|
});
|
|
4087
4325
|
if (!response.ok) {
|
|
4088
4326
|
const errorBody = await response.text().catch(() => "");
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
return { pass: false, reason: msg };
|
|
4327
|
+
console.error(`[moderation] Anthropic API error: ${response.status} ${response.statusText}`, errorBody);
|
|
4328
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4092
4329
|
}
|
|
4093
4330
|
const data = await response.json();
|
|
4094
4331
|
const rawText = data.content?.[0]?.text ?? "";
|
|
4095
|
-
const text = rawText.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4332
|
+
const text = ("{" + rawText).replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4096
4333
|
const parsed = JSON.parse(text);
|
|
4097
4334
|
const result = {
|
|
4098
4335
|
pass: parsed.pass === true,
|
|
@@ -4106,9 +4343,8 @@ function createModerationClient(options) {
|
|
|
4106
4343
|
cache.set(hash, result);
|
|
4107
4344
|
return result;
|
|
4108
4345
|
} catch (err2) {
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
return { pass: false, reason: msg };
|
|
4346
|
+
console.error("[moderation] Unexpected error:", err2);
|
|
4347
|
+
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4112
4348
|
}
|
|
4113
4349
|
}
|
|
4114
4350
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/remember-mcp",
|
|
3
|
-
"version": "3.15.
|
|
3
|
+
"version": "3.15.4",
|
|
4
4
|
"description": "Multi-tenant memory system MCP server with vector search and relationships",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
53
53
|
"@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
|
|
54
54
|
"@prmichaelsen/mcp-auth": "^7.0.4",
|
|
55
|
-
"@prmichaelsen/remember-core": "^0.
|
|
55
|
+
"@prmichaelsen/remember-core": "^0.35.2",
|
|
56
56
|
"dotenv": "^16.4.5",
|
|
57
57
|
"uuid": "^13.0.0",
|
|
58
58
|
"weaviate-client": "^3.2.0"
|