@prmichaelsen/remember-mcp 3.15.2 → 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 +304 -45
- package/dist/server.js +304 -45
- package/package.json +2 -2
package/dist/server-factory.js
CHANGED
|
@@ -1397,6 +1397,24 @@ var ValidationError = class extends AppError {
|
|
|
1397
1397
|
this.fields = fields;
|
|
1398
1398
|
}
|
|
1399
1399
|
};
|
|
1400
|
+
var NotFoundError = class extends AppError {
|
|
1401
|
+
resource;
|
|
1402
|
+
id;
|
|
1403
|
+
kind = "not_found";
|
|
1404
|
+
constructor(resource, id) {
|
|
1405
|
+
super(`${resource} not found: ${id}`, { resource, id });
|
|
1406
|
+
this.resource = resource;
|
|
1407
|
+
this.id = id;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
var ForbiddenError = class extends AppError {
|
|
1411
|
+
requiredRole;
|
|
1412
|
+
kind = "forbidden";
|
|
1413
|
+
constructor(message = "Access denied", requiredRole) {
|
|
1414
|
+
super(message, { requiredRole });
|
|
1415
|
+
this.requiredRole = requiredRole;
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1400
1418
|
|
|
1401
1419
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
|
|
1402
1420
|
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;
|
|
@@ -2313,6 +2331,38 @@ async function getSpaceConfig(id, type, logger2) {
|
|
|
2313
2331
|
// node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
|
|
2314
2332
|
import { Filters as Filters2 } from "weaviate-client";
|
|
2315
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
|
+
|
|
2316
2366
|
// node_modules/@prmichaelsen/remember-core/dist/database/weaviate/client.js
|
|
2317
2367
|
import weaviate2 from "weaviate-client";
|
|
2318
2368
|
var ALL_MEMORY_PROPERTIES = [
|
|
@@ -2693,6 +2743,72 @@ var MemoryService = class {
|
|
|
2693
2743
|
limit
|
|
2694
2744
|
};
|
|
2695
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
|
+
}
|
|
2696
2812
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2697
2813
|
async findSimilar(input) {
|
|
2698
2814
|
if (!input.memory_id && !input.text)
|
|
@@ -3272,32 +3388,32 @@ var SpaceService = class {
|
|
|
3272
3388
|
const spaces = input.spaces || [];
|
|
3273
3389
|
const groups = input.groups || [];
|
|
3274
3390
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3275
|
-
throw new
|
|
3391
|
+
throw new ValidationError("Must specify at least one space or group to publish to");
|
|
3276
3392
|
}
|
|
3277
3393
|
if (spaces.length > 0) {
|
|
3278
3394
|
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
3279
3395
|
if (invalidSpaces.length > 0) {
|
|
3280
|
-
throw new
|
|
3396
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3281
3397
|
}
|
|
3282
3398
|
}
|
|
3283
3399
|
if (groups.length > 0) {
|
|
3284
3400
|
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
3285
3401
|
if (invalidGroups.length > 0) {
|
|
3286
|
-
throw new
|
|
3402
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
3287
3403
|
}
|
|
3288
3404
|
}
|
|
3289
3405
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3290
3406
|
if (!memory)
|
|
3291
|
-
throw new
|
|
3407
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3292
3408
|
if (memory.properties.user_id !== this.userId)
|
|
3293
|
-
throw new
|
|
3409
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3294
3410
|
if (memory.properties.doc_type !== "memory")
|
|
3295
|
-
throw new
|
|
3411
|
+
throw new ValidationError("Only memories can be published");
|
|
3296
3412
|
const memoryContentType = memory.properties.content_type;
|
|
3297
3413
|
for (const spaceId of spaces) {
|
|
3298
3414
|
const requiredType = SPACE_CONTENT_TYPE_RESTRICTIONS[spaceId];
|
|
3299
3415
|
if (requiredType && memoryContentType !== requiredType) {
|
|
3300
|
-
throw new
|
|
3416
|
+
throw new ValidationError(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
|
|
3301
3417
|
}
|
|
3302
3418
|
}
|
|
3303
3419
|
await this.checkModeration(memory.properties.content);
|
|
@@ -3320,25 +3436,25 @@ var SpaceService = class {
|
|
|
3320
3436
|
const spaces = input.spaces || [];
|
|
3321
3437
|
const groups = input.groups || [];
|
|
3322
3438
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3323
|
-
throw new
|
|
3439
|
+
throw new ValidationError("Must specify at least one space or group to retract from");
|
|
3324
3440
|
}
|
|
3325
3441
|
if (groups.length > 0) {
|
|
3326
3442
|
const invalidGroups = groups.filter((g) => g.includes("."));
|
|
3327
3443
|
if (invalidGroups.length > 0) {
|
|
3328
|
-
throw new
|
|
3444
|
+
throw new ValidationError(`Group IDs cannot contain dots: ${invalidGroups.join(", ")}`);
|
|
3329
3445
|
}
|
|
3330
3446
|
}
|
|
3331
3447
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3332
3448
|
if (!memory)
|
|
3333
|
-
throw new
|
|
3449
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3334
3450
|
if (memory.properties.user_id !== this.userId)
|
|
3335
|
-
throw new
|
|
3451
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3336
3452
|
const currentSpaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
3337
3453
|
const currentGroupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
3338
3454
|
const notPublishedSpaces = spaces.filter((s) => !currentSpaceIds.includes(s));
|
|
3339
3455
|
const notPublishedGroups = groups.filter((g) => !currentGroupIds.includes(g));
|
|
3340
3456
|
if (notPublishedSpaces.length > 0 || notPublishedGroups.length > 0) {
|
|
3341
|
-
throw new
|
|
3457
|
+
throw new ValidationError(`Memory is not published to some destinations. Not in spaces: [${notPublishedSpaces.join(", ")}], Not in groups: [${notPublishedGroups.join(", ")}]`);
|
|
3342
3458
|
}
|
|
3343
3459
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "retract_memory", {
|
|
3344
3460
|
memory_id: input.memory_id,
|
|
@@ -3359,13 +3475,13 @@ var SpaceService = class {
|
|
|
3359
3475
|
async revise(input) {
|
|
3360
3476
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3361
3477
|
if (!memory)
|
|
3362
|
-
throw new
|
|
3478
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3363
3479
|
if (memory.properties.user_id !== this.userId)
|
|
3364
|
-
throw new
|
|
3480
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3365
3481
|
const spaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
3366
3482
|
const groupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
3367
3483
|
if (spaceIds.length === 0 && groupIds.length === 0) {
|
|
3368
|
-
throw new
|
|
3484
|
+
throw new ValidationError("Memory has no published copies to revise. Publish first with publish().");
|
|
3369
3485
|
}
|
|
3370
3486
|
await this.checkModeration(memory.properties.content);
|
|
3371
3487
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
|
|
@@ -3385,7 +3501,7 @@ var SpaceService = class {
|
|
|
3385
3501
|
async confirm(input) {
|
|
3386
3502
|
const request = await this.confirmationTokenService.confirmRequest(this.userId, input.token);
|
|
3387
3503
|
if (!request) {
|
|
3388
|
-
throw new
|
|
3504
|
+
throw new ValidationError("Invalid or expired confirmation token");
|
|
3389
3505
|
}
|
|
3390
3506
|
if (request.action === "publish_memory") {
|
|
3391
3507
|
return this.executePublish(request);
|
|
@@ -3396,31 +3512,31 @@ var SpaceService = class {
|
|
|
3396
3512
|
if (request.action === "revise_memory") {
|
|
3397
3513
|
return this.executeRevise(request);
|
|
3398
3514
|
}
|
|
3399
|
-
throw new
|
|
3515
|
+
throw new ValidationError(`Unknown action type: ${request.action}`);
|
|
3400
3516
|
}
|
|
3401
3517
|
// ── Deny ────────────────────────────────────────────────────────────
|
|
3402
3518
|
async deny(input) {
|
|
3403
3519
|
const success = await this.confirmationTokenService.denyRequest(this.userId, input.token);
|
|
3404
3520
|
if (!success) {
|
|
3405
|
-
throw new
|
|
3521
|
+
throw new NotFoundError("Token", input.token);
|
|
3406
3522
|
}
|
|
3407
3523
|
return { success: true };
|
|
3408
3524
|
}
|
|
3409
3525
|
// ── Moderate ────────────────────────────────────────────────────────
|
|
3410
3526
|
async moderate(input, authContext) {
|
|
3411
3527
|
if (!input.space_id && !input.group_id) {
|
|
3412
|
-
throw new
|
|
3528
|
+
throw new ValidationError("Must specify either space_id or group_id");
|
|
3413
3529
|
}
|
|
3414
3530
|
if (!ACTION_TO_STATUS[input.action]) {
|
|
3415
|
-
throw new
|
|
3531
|
+
throw new ValidationError(`Invalid action: ${input.action}. Must be approve, reject, or remove`);
|
|
3416
3532
|
}
|
|
3417
3533
|
if (input.group_id) {
|
|
3418
3534
|
if (!canModerate(authContext, input.group_id)) {
|
|
3419
|
-
throw new
|
|
3535
|
+
throw new ForbiddenError(`Moderator access required for group ${input.group_id}`);
|
|
3420
3536
|
}
|
|
3421
3537
|
} else if (input.space_id) {
|
|
3422
3538
|
if (!canModerateAny(authContext)) {
|
|
3423
|
-
throw new
|
|
3539
|
+
throw new ForbiddenError("Moderator access required to moderate memories in spaces");
|
|
3424
3540
|
}
|
|
3425
3541
|
}
|
|
3426
3542
|
let collection;
|
|
@@ -3432,8 +3548,7 @@ var SpaceService = class {
|
|
|
3432
3548
|
}
|
|
3433
3549
|
const memory = await fetchMemoryWithAllProperties(collection, input.memory_id);
|
|
3434
3550
|
if (!memory) {
|
|
3435
|
-
|
|
3436
|
-
throw new Error(`Published memory ${input.memory_id} not found in ${location2}`);
|
|
3551
|
+
throw new NotFoundError("Published memory", input.memory_id);
|
|
3437
3552
|
}
|
|
3438
3553
|
const newStatus = ACTION_TO_STATUS[input.action];
|
|
3439
3554
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3472,24 +3587,24 @@ var SpaceService = class {
|
|
|
3472
3587
|
if (spaces.length > 0) {
|
|
3473
3588
|
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
3474
3589
|
if (invalidSpaces.length > 0) {
|
|
3475
|
-
throw new
|
|
3590
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3476
3591
|
}
|
|
3477
3592
|
}
|
|
3478
3593
|
if (groups.length > 0) {
|
|
3479
3594
|
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
3480
3595
|
if (invalidGroups.length > 0) {
|
|
3481
|
-
throw new
|
|
3596
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
3482
3597
|
}
|
|
3483
3598
|
}
|
|
3484
3599
|
const moderationFilter = input.moderation_filter || "approved";
|
|
3485
3600
|
if (moderationFilter !== "approved") {
|
|
3486
3601
|
for (const groupId of groups) {
|
|
3487
3602
|
if (!canModerate(authContext, groupId)) {
|
|
3488
|
-
throw new
|
|
3603
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in group ${groupId}`);
|
|
3489
3604
|
}
|
|
3490
3605
|
}
|
|
3491
3606
|
if ((spaces.length > 0 || groups.length === 0) && !canModerateAny(authContext)) {
|
|
3492
|
-
throw new
|
|
3607
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in spaces`);
|
|
3493
3608
|
}
|
|
3494
3609
|
}
|
|
3495
3610
|
const fetchLimit = (limit + offset) * Math.max(1, groups.length + (spaces.length > 0 || groups.length === 0 ? 1 : 0));
|
|
@@ -3549,16 +3664,16 @@ var SpaceService = class {
|
|
|
3549
3664
|
// ── Query Space ─────────────────────────────────────────────────────
|
|
3550
3665
|
async query(input, authContext) {
|
|
3551
3666
|
if (!input.question?.trim())
|
|
3552
|
-
throw new
|
|
3667
|
+
throw new ValidationError("Question cannot be empty");
|
|
3553
3668
|
if (input.spaces.length === 0)
|
|
3554
|
-
throw new
|
|
3669
|
+
throw new ValidationError("Must specify at least one space to query");
|
|
3555
3670
|
const invalidSpaces = input.spaces.filter((s) => !isValidSpaceId(s));
|
|
3556
3671
|
if (invalidSpaces.length > 0) {
|
|
3557
|
-
throw new
|
|
3672
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3558
3673
|
}
|
|
3559
3674
|
const moderationFilterValue = input.moderation_filter || "approved";
|
|
3560
3675
|
if (moderationFilterValue !== "approved" && !canModerateAny(authContext)) {
|
|
3561
|
-
throw new
|
|
3676
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilterValue} memories in spaces`);
|
|
3562
3677
|
}
|
|
3563
3678
|
const publicCollection = await ensurePublicCollection(this.weaviateClient);
|
|
3564
3679
|
const filterList = [];
|
|
@@ -3609,13 +3724,13 @@ var SpaceService = class {
|
|
|
3609
3724
|
const spaces = request.payload.spaces || [];
|
|
3610
3725
|
const groups = request.payload.groups || [];
|
|
3611
3726
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3612
|
-
throw new
|
|
3727
|
+
throw new ValidationError("No destinations in publish request");
|
|
3613
3728
|
}
|
|
3614
3729
|
const originalMemory = await fetchMemoryWithAllProperties(this.userCollection, request.payload.memory_id);
|
|
3615
3730
|
if (!originalMemory)
|
|
3616
|
-
throw new
|
|
3731
|
+
throw new NotFoundError("Memory", request.payload.memory_id);
|
|
3617
3732
|
if (originalMemory.properties.user_id !== this.userId)
|
|
3618
|
-
throw new
|
|
3733
|
+
throw new ForbiddenError("Permission denied");
|
|
3619
3734
|
const compositeId = generateCompositeId(this.userId, request.payload.memory_id);
|
|
3620
3735
|
const weaviateId = compositeIdToUuid(compositeId);
|
|
3621
3736
|
const existingSpaceIds = Array.isArray(originalMemory.properties.space_ids) ? originalMemory.properties.space_ids : [];
|
|
@@ -3751,7 +3866,7 @@ var SpaceService = class {
|
|
|
3751
3866
|
const groups = request.payload.groups || [];
|
|
3752
3867
|
const sourceMemory = await fetchMemoryWithAllProperties(this.userCollection, request.payload.memory_id);
|
|
3753
3868
|
if (!sourceMemory)
|
|
3754
|
-
throw new
|
|
3869
|
+
throw new NotFoundError("Memory", request.payload.memory_id);
|
|
3755
3870
|
const currentSpaceIds = Array.isArray(sourceMemory.properties.space_ids) ? sourceMemory.properties.space_ids : [];
|
|
3756
3871
|
const currentGroupIds = Array.isArray(sourceMemory.properties.group_ids) ? sourceMemory.properties.group_ids : [];
|
|
3757
3872
|
const compositeId = generateCompositeId(this.userId, request.payload.memory_id);
|
|
@@ -3831,9 +3946,9 @@ var SpaceService = class {
|
|
|
3831
3946
|
const { memory_id, space_ids = [], group_ids = [] } = request.payload;
|
|
3832
3947
|
const sourceMemory = await fetchMemoryWithAllProperties(this.userCollection, memory_id);
|
|
3833
3948
|
if (!sourceMemory)
|
|
3834
|
-
throw new
|
|
3949
|
+
throw new NotFoundError("Memory", memory_id);
|
|
3835
3950
|
if (sourceMemory.properties.user_id !== this.userId)
|
|
3836
|
-
throw new
|
|
3951
|
+
throw new ForbiddenError("Permission denied");
|
|
3837
3952
|
const newContent = String(sourceMemory.properties.content ?? "");
|
|
3838
3953
|
const revisedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3839
3954
|
const compositeId = generateCompositeId(this.userId, memory_id);
|
|
@@ -3891,6 +4006,135 @@ var SpaceService = class {
|
|
|
3891
4006
|
results
|
|
3892
4007
|
};
|
|
3893
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
|
+
}
|
|
3894
4138
|
// ── Private: Build Base Filters ─────────────────────────────────────
|
|
3895
4139
|
buildBaseFilters(collection, input) {
|
|
3896
4140
|
const filterList = [];
|
|
@@ -3928,6 +4172,10 @@ var SpaceService = class {
|
|
|
3928
4172
|
const opts = { limit };
|
|
3929
4173
|
if (filters)
|
|
3930
4174
|
opts.filters = filters;
|
|
4175
|
+
const isWildcard = !query.trim() || query === "*";
|
|
4176
|
+
if (isWildcard) {
|
|
4177
|
+
return (await collection.query.bm25("*", opts)).objects;
|
|
4178
|
+
}
|
|
3931
4179
|
switch (searchType) {
|
|
3932
4180
|
case "bm25":
|
|
3933
4181
|
return (await collection.query.bm25(query, opts)).objects;
|
|
@@ -4030,10 +4278,13 @@ Content to evaluate:
|
|
|
4030
4278
|
${content}
|
|
4031
4279
|
---
|
|
4032
4280
|
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
{"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.`;
|
|
4037
4288
|
}
|
|
4038
4289
|
var DEFAULT_CACHE_MAX = 1e3;
|
|
4039
4290
|
function hashContent(content) {
|
|
@@ -4060,14 +4311,21 @@ function createModerationClient(options) {
|
|
|
4060
4311
|
body: JSON.stringify({
|
|
4061
4312
|
model,
|
|
4062
4313
|
max_tokens: 256,
|
|
4063
|
-
|
|
4314
|
+
temperature: 0,
|
|
4315
|
+
messages: [
|
|
4316
|
+
{ role: "user", content: buildModerationPrompt(content) },
|
|
4317
|
+
{ role: "assistant", content: "{" }
|
|
4318
|
+
]
|
|
4064
4319
|
})
|
|
4065
4320
|
});
|
|
4066
4321
|
if (!response.ok) {
|
|
4322
|
+
const errorBody = await response.text().catch(() => "");
|
|
4323
|
+
console.error(`[moderation] Anthropic API error: ${response.status} ${response.statusText}`, errorBody);
|
|
4067
4324
|
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4068
4325
|
}
|
|
4069
4326
|
const data = await response.json();
|
|
4070
|
-
const
|
|
4327
|
+
const rawText = data.content?.[0]?.text ?? "";
|
|
4328
|
+
const text = ("{" + rawText).replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4071
4329
|
const parsed = JSON.parse(text);
|
|
4072
4330
|
const result = {
|
|
4073
4331
|
pass: parsed.pass === true,
|
|
@@ -4080,7 +4338,8 @@ function createModerationClient(options) {
|
|
|
4080
4338
|
}
|
|
4081
4339
|
cache.set(hash, result);
|
|
4082
4340
|
return result;
|
|
4083
|
-
} catch {
|
|
4341
|
+
} catch (err2) {
|
|
4342
|
+
console.error("[moderation] Unexpected error:", err2);
|
|
4084
4343
|
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4085
4344
|
}
|
|
4086
4345
|
}
|
package/dist/server.js
CHANGED
|
@@ -1401,6 +1401,24 @@ var ValidationError = class extends AppError {
|
|
|
1401
1401
|
this.fields = fields;
|
|
1402
1402
|
}
|
|
1403
1403
|
};
|
|
1404
|
+
var NotFoundError = class extends AppError {
|
|
1405
|
+
resource;
|
|
1406
|
+
id;
|
|
1407
|
+
kind = "not_found";
|
|
1408
|
+
constructor(resource, id) {
|
|
1409
|
+
super(`${resource} not found: ${id}`, { resource, id });
|
|
1410
|
+
this.resource = resource;
|
|
1411
|
+
this.id = id;
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
var ForbiddenError = class extends AppError {
|
|
1415
|
+
requiredRole;
|
|
1416
|
+
kind = "forbidden";
|
|
1417
|
+
constructor(message = "Access denied", requiredRole) {
|
|
1418
|
+
super(message, { requiredRole });
|
|
1419
|
+
this.requiredRole = requiredRole;
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1404
1422
|
|
|
1405
1423
|
// node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
|
|
1406
1424
|
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;
|
|
@@ -2317,6 +2335,38 @@ async function getSpaceConfig(id, type, logger2) {
|
|
|
2317
2335
|
// node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
|
|
2318
2336
|
import { Filters as Filters2 } from "weaviate-client";
|
|
2319
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
|
+
|
|
2320
2370
|
// node_modules/@prmichaelsen/remember-core/dist/database/weaviate/client.js
|
|
2321
2371
|
import weaviate2 from "weaviate-client";
|
|
2322
2372
|
var ALL_MEMORY_PROPERTIES = [
|
|
@@ -2697,6 +2747,72 @@ var MemoryService = class {
|
|
|
2697
2747
|
limit
|
|
2698
2748
|
};
|
|
2699
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
|
+
}
|
|
2700
2816
|
// ── Find Similar (vector) ──────────────────────────────────────────
|
|
2701
2817
|
async findSimilar(input) {
|
|
2702
2818
|
if (!input.memory_id && !input.text)
|
|
@@ -3276,32 +3392,32 @@ var SpaceService = class {
|
|
|
3276
3392
|
const spaces = input.spaces || [];
|
|
3277
3393
|
const groups = input.groups || [];
|
|
3278
3394
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3279
|
-
throw new
|
|
3395
|
+
throw new ValidationError("Must specify at least one space or group to publish to");
|
|
3280
3396
|
}
|
|
3281
3397
|
if (spaces.length > 0) {
|
|
3282
3398
|
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
3283
3399
|
if (invalidSpaces.length > 0) {
|
|
3284
|
-
throw new
|
|
3400
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3285
3401
|
}
|
|
3286
3402
|
}
|
|
3287
3403
|
if (groups.length > 0) {
|
|
3288
3404
|
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
3289
3405
|
if (invalidGroups.length > 0) {
|
|
3290
|
-
throw new
|
|
3406
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
3291
3407
|
}
|
|
3292
3408
|
}
|
|
3293
3409
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3294
3410
|
if (!memory)
|
|
3295
|
-
throw new
|
|
3411
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3296
3412
|
if (memory.properties.user_id !== this.userId)
|
|
3297
|
-
throw new
|
|
3413
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3298
3414
|
if (memory.properties.doc_type !== "memory")
|
|
3299
|
-
throw new
|
|
3415
|
+
throw new ValidationError("Only memories can be published");
|
|
3300
3416
|
const memoryContentType = memory.properties.content_type;
|
|
3301
3417
|
for (const spaceId of spaces) {
|
|
3302
3418
|
const requiredType = SPACE_CONTENT_TYPE_RESTRICTIONS[spaceId];
|
|
3303
3419
|
if (requiredType && memoryContentType !== requiredType) {
|
|
3304
|
-
throw new
|
|
3420
|
+
throw new ValidationError(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
|
|
3305
3421
|
}
|
|
3306
3422
|
}
|
|
3307
3423
|
await this.checkModeration(memory.properties.content);
|
|
@@ -3324,25 +3440,25 @@ var SpaceService = class {
|
|
|
3324
3440
|
const spaces = input.spaces || [];
|
|
3325
3441
|
const groups = input.groups || [];
|
|
3326
3442
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3327
|
-
throw new
|
|
3443
|
+
throw new ValidationError("Must specify at least one space or group to retract from");
|
|
3328
3444
|
}
|
|
3329
3445
|
if (groups.length > 0) {
|
|
3330
3446
|
const invalidGroups = groups.filter((g) => g.includes("."));
|
|
3331
3447
|
if (invalidGroups.length > 0) {
|
|
3332
|
-
throw new
|
|
3448
|
+
throw new ValidationError(`Group IDs cannot contain dots: ${invalidGroups.join(", ")}`);
|
|
3333
3449
|
}
|
|
3334
3450
|
}
|
|
3335
3451
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3336
3452
|
if (!memory)
|
|
3337
|
-
throw new
|
|
3453
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3338
3454
|
if (memory.properties.user_id !== this.userId)
|
|
3339
|
-
throw new
|
|
3455
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3340
3456
|
const currentSpaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
3341
3457
|
const currentGroupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
3342
3458
|
const notPublishedSpaces = spaces.filter((s) => !currentSpaceIds.includes(s));
|
|
3343
3459
|
const notPublishedGroups = groups.filter((g) => !currentGroupIds.includes(g));
|
|
3344
3460
|
if (notPublishedSpaces.length > 0 || notPublishedGroups.length > 0) {
|
|
3345
|
-
throw new
|
|
3461
|
+
throw new ValidationError(`Memory is not published to some destinations. Not in spaces: [${notPublishedSpaces.join(", ")}], Not in groups: [${notPublishedGroups.join(", ")}]`);
|
|
3346
3462
|
}
|
|
3347
3463
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "retract_memory", {
|
|
3348
3464
|
memory_id: input.memory_id,
|
|
@@ -3363,13 +3479,13 @@ var SpaceService = class {
|
|
|
3363
3479
|
async revise(input) {
|
|
3364
3480
|
const memory = await fetchMemoryWithAllProperties(this.userCollection, input.memory_id);
|
|
3365
3481
|
if (!memory)
|
|
3366
|
-
throw new
|
|
3482
|
+
throw new NotFoundError("Memory", input.memory_id);
|
|
3367
3483
|
if (memory.properties.user_id !== this.userId)
|
|
3368
|
-
throw new
|
|
3484
|
+
throw new ForbiddenError("Permission denied: not memory owner");
|
|
3369
3485
|
const spaceIds = Array.isArray(memory.properties.space_ids) ? memory.properties.space_ids : [];
|
|
3370
3486
|
const groupIds = Array.isArray(memory.properties.group_ids) ? memory.properties.group_ids : [];
|
|
3371
3487
|
if (spaceIds.length === 0 && groupIds.length === 0) {
|
|
3372
|
-
throw new
|
|
3488
|
+
throw new ValidationError("Memory has no published copies to revise. Publish first with publish().");
|
|
3373
3489
|
}
|
|
3374
3490
|
await this.checkModeration(memory.properties.content);
|
|
3375
3491
|
const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
|
|
@@ -3389,7 +3505,7 @@ var SpaceService = class {
|
|
|
3389
3505
|
async confirm(input) {
|
|
3390
3506
|
const request = await this.confirmationTokenService.confirmRequest(this.userId, input.token);
|
|
3391
3507
|
if (!request) {
|
|
3392
|
-
throw new
|
|
3508
|
+
throw new ValidationError("Invalid or expired confirmation token");
|
|
3393
3509
|
}
|
|
3394
3510
|
if (request.action === "publish_memory") {
|
|
3395
3511
|
return this.executePublish(request);
|
|
@@ -3400,31 +3516,31 @@ var SpaceService = class {
|
|
|
3400
3516
|
if (request.action === "revise_memory") {
|
|
3401
3517
|
return this.executeRevise(request);
|
|
3402
3518
|
}
|
|
3403
|
-
throw new
|
|
3519
|
+
throw new ValidationError(`Unknown action type: ${request.action}`);
|
|
3404
3520
|
}
|
|
3405
3521
|
// ── Deny ────────────────────────────────────────────────────────────
|
|
3406
3522
|
async deny(input) {
|
|
3407
3523
|
const success = await this.confirmationTokenService.denyRequest(this.userId, input.token);
|
|
3408
3524
|
if (!success) {
|
|
3409
|
-
throw new
|
|
3525
|
+
throw new NotFoundError("Token", input.token);
|
|
3410
3526
|
}
|
|
3411
3527
|
return { success: true };
|
|
3412
3528
|
}
|
|
3413
3529
|
// ── Moderate ────────────────────────────────────────────────────────
|
|
3414
3530
|
async moderate(input, authContext) {
|
|
3415
3531
|
if (!input.space_id && !input.group_id) {
|
|
3416
|
-
throw new
|
|
3532
|
+
throw new ValidationError("Must specify either space_id or group_id");
|
|
3417
3533
|
}
|
|
3418
3534
|
if (!ACTION_TO_STATUS[input.action]) {
|
|
3419
|
-
throw new
|
|
3535
|
+
throw new ValidationError(`Invalid action: ${input.action}. Must be approve, reject, or remove`);
|
|
3420
3536
|
}
|
|
3421
3537
|
if (input.group_id) {
|
|
3422
3538
|
if (!canModerate(authContext, input.group_id)) {
|
|
3423
|
-
throw new
|
|
3539
|
+
throw new ForbiddenError(`Moderator access required for group ${input.group_id}`);
|
|
3424
3540
|
}
|
|
3425
3541
|
} else if (input.space_id) {
|
|
3426
3542
|
if (!canModerateAny(authContext)) {
|
|
3427
|
-
throw new
|
|
3543
|
+
throw new ForbiddenError("Moderator access required to moderate memories in spaces");
|
|
3428
3544
|
}
|
|
3429
3545
|
}
|
|
3430
3546
|
let collection;
|
|
@@ -3436,8 +3552,7 @@ var SpaceService = class {
|
|
|
3436
3552
|
}
|
|
3437
3553
|
const memory = await fetchMemoryWithAllProperties(collection, input.memory_id);
|
|
3438
3554
|
if (!memory) {
|
|
3439
|
-
|
|
3440
|
-
throw new Error(`Published memory ${input.memory_id} not found in ${location2}`);
|
|
3555
|
+
throw new NotFoundError("Published memory", input.memory_id);
|
|
3441
3556
|
}
|
|
3442
3557
|
const newStatus = ACTION_TO_STATUS[input.action];
|
|
3443
3558
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3476,24 +3591,24 @@ var SpaceService = class {
|
|
|
3476
3591
|
if (spaces.length > 0) {
|
|
3477
3592
|
const invalidSpaces = spaces.filter((s) => !isValidSpaceId(s));
|
|
3478
3593
|
if (invalidSpaces.length > 0) {
|
|
3479
|
-
throw new
|
|
3594
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3480
3595
|
}
|
|
3481
3596
|
}
|
|
3482
3597
|
if (groups.length > 0) {
|
|
3483
3598
|
const invalidGroups = groups.filter((g) => !g || g.includes(".") || g.trim() === "");
|
|
3484
3599
|
if (invalidGroups.length > 0) {
|
|
3485
|
-
throw new
|
|
3600
|
+
throw new ValidationError("Group IDs cannot be empty or contain dots");
|
|
3486
3601
|
}
|
|
3487
3602
|
}
|
|
3488
3603
|
const moderationFilter = input.moderation_filter || "approved";
|
|
3489
3604
|
if (moderationFilter !== "approved") {
|
|
3490
3605
|
for (const groupId of groups) {
|
|
3491
3606
|
if (!canModerate(authContext, groupId)) {
|
|
3492
|
-
throw new
|
|
3607
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in group ${groupId}`);
|
|
3493
3608
|
}
|
|
3494
3609
|
}
|
|
3495
3610
|
if ((spaces.length > 0 || groups.length === 0) && !canModerateAny(authContext)) {
|
|
3496
|
-
throw new
|
|
3611
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilter} memories in spaces`);
|
|
3497
3612
|
}
|
|
3498
3613
|
}
|
|
3499
3614
|
const fetchLimit = (limit + offset) * Math.max(1, groups.length + (spaces.length > 0 || groups.length === 0 ? 1 : 0));
|
|
@@ -3553,16 +3668,16 @@ var SpaceService = class {
|
|
|
3553
3668
|
// ── Query Space ─────────────────────────────────────────────────────
|
|
3554
3669
|
async query(input, authContext) {
|
|
3555
3670
|
if (!input.question?.trim())
|
|
3556
|
-
throw new
|
|
3671
|
+
throw new ValidationError("Question cannot be empty");
|
|
3557
3672
|
if (input.spaces.length === 0)
|
|
3558
|
-
throw new
|
|
3673
|
+
throw new ValidationError("Must specify at least one space to query");
|
|
3559
3674
|
const invalidSpaces = input.spaces.filter((s) => !isValidSpaceId(s));
|
|
3560
3675
|
if (invalidSpaces.length > 0) {
|
|
3561
|
-
throw new
|
|
3676
|
+
throw new ValidationError(`Invalid space IDs: ${invalidSpaces.join(", ")}`, { spaces: invalidSpaces });
|
|
3562
3677
|
}
|
|
3563
3678
|
const moderationFilterValue = input.moderation_filter || "approved";
|
|
3564
3679
|
if (moderationFilterValue !== "approved" && !canModerateAny(authContext)) {
|
|
3565
|
-
throw new
|
|
3680
|
+
throw new ForbiddenError(`Moderator access required to view ${moderationFilterValue} memories in spaces`);
|
|
3566
3681
|
}
|
|
3567
3682
|
const publicCollection = await ensurePublicCollection(this.weaviateClient);
|
|
3568
3683
|
const filterList = [];
|
|
@@ -3613,13 +3728,13 @@ var SpaceService = class {
|
|
|
3613
3728
|
const spaces = request.payload.spaces || [];
|
|
3614
3729
|
const groups = request.payload.groups || [];
|
|
3615
3730
|
if (spaces.length === 0 && groups.length === 0) {
|
|
3616
|
-
throw new
|
|
3731
|
+
throw new ValidationError("No destinations in publish request");
|
|
3617
3732
|
}
|
|
3618
3733
|
const originalMemory = await fetchMemoryWithAllProperties(this.userCollection, request.payload.memory_id);
|
|
3619
3734
|
if (!originalMemory)
|
|
3620
|
-
throw new
|
|
3735
|
+
throw new NotFoundError("Memory", request.payload.memory_id);
|
|
3621
3736
|
if (originalMemory.properties.user_id !== this.userId)
|
|
3622
|
-
throw new
|
|
3737
|
+
throw new ForbiddenError("Permission denied");
|
|
3623
3738
|
const compositeId = generateCompositeId(this.userId, request.payload.memory_id);
|
|
3624
3739
|
const weaviateId = compositeIdToUuid(compositeId);
|
|
3625
3740
|
const existingSpaceIds = Array.isArray(originalMemory.properties.space_ids) ? originalMemory.properties.space_ids : [];
|
|
@@ -3755,7 +3870,7 @@ var SpaceService = class {
|
|
|
3755
3870
|
const groups = request.payload.groups || [];
|
|
3756
3871
|
const sourceMemory = await fetchMemoryWithAllProperties(this.userCollection, request.payload.memory_id);
|
|
3757
3872
|
if (!sourceMemory)
|
|
3758
|
-
throw new
|
|
3873
|
+
throw new NotFoundError("Memory", request.payload.memory_id);
|
|
3759
3874
|
const currentSpaceIds = Array.isArray(sourceMemory.properties.space_ids) ? sourceMemory.properties.space_ids : [];
|
|
3760
3875
|
const currentGroupIds = Array.isArray(sourceMemory.properties.group_ids) ? sourceMemory.properties.group_ids : [];
|
|
3761
3876
|
const compositeId = generateCompositeId(this.userId, request.payload.memory_id);
|
|
@@ -3835,9 +3950,9 @@ var SpaceService = class {
|
|
|
3835
3950
|
const { memory_id, space_ids = [], group_ids = [] } = request.payload;
|
|
3836
3951
|
const sourceMemory = await fetchMemoryWithAllProperties(this.userCollection, memory_id);
|
|
3837
3952
|
if (!sourceMemory)
|
|
3838
|
-
throw new
|
|
3953
|
+
throw new NotFoundError("Memory", memory_id);
|
|
3839
3954
|
if (sourceMemory.properties.user_id !== this.userId)
|
|
3840
|
-
throw new
|
|
3955
|
+
throw new ForbiddenError("Permission denied");
|
|
3841
3956
|
const newContent = String(sourceMemory.properties.content ?? "");
|
|
3842
3957
|
const revisedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3843
3958
|
const compositeId = generateCompositeId(this.userId, memory_id);
|
|
@@ -3895,6 +4010,135 @@ var SpaceService = class {
|
|
|
3895
4010
|
results
|
|
3896
4011
|
};
|
|
3897
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
|
+
}
|
|
3898
4142
|
// ── Private: Build Base Filters ─────────────────────────────────────
|
|
3899
4143
|
buildBaseFilters(collection, input) {
|
|
3900
4144
|
const filterList = [];
|
|
@@ -3932,6 +4176,10 @@ var SpaceService = class {
|
|
|
3932
4176
|
const opts = { limit };
|
|
3933
4177
|
if (filters)
|
|
3934
4178
|
opts.filters = filters;
|
|
4179
|
+
const isWildcard = !query.trim() || query === "*";
|
|
4180
|
+
if (isWildcard) {
|
|
4181
|
+
return (await collection.query.bm25("*", opts)).objects;
|
|
4182
|
+
}
|
|
3935
4183
|
switch (searchType) {
|
|
3936
4184
|
case "bm25":
|
|
3937
4185
|
return (await collection.query.bm25(query, opts)).objects;
|
|
@@ -4034,10 +4282,13 @@ Content to evaluate:
|
|
|
4034
4282
|
${content}
|
|
4035
4283
|
---
|
|
4036
4284
|
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
{"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.`;
|
|
4041
4292
|
}
|
|
4042
4293
|
var DEFAULT_CACHE_MAX = 1e3;
|
|
4043
4294
|
function hashContent(content) {
|
|
@@ -4064,14 +4315,21 @@ function createModerationClient(options) {
|
|
|
4064
4315
|
body: JSON.stringify({
|
|
4065
4316
|
model,
|
|
4066
4317
|
max_tokens: 256,
|
|
4067
|
-
|
|
4318
|
+
temperature: 0,
|
|
4319
|
+
messages: [
|
|
4320
|
+
{ role: "user", content: buildModerationPrompt(content) },
|
|
4321
|
+
{ role: "assistant", content: "{" }
|
|
4322
|
+
]
|
|
4068
4323
|
})
|
|
4069
4324
|
});
|
|
4070
4325
|
if (!response.ok) {
|
|
4326
|
+
const errorBody = await response.text().catch(() => "");
|
|
4327
|
+
console.error(`[moderation] Anthropic API error: ${response.status} ${response.statusText}`, errorBody);
|
|
4071
4328
|
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4072
4329
|
}
|
|
4073
4330
|
const data = await response.json();
|
|
4074
|
-
const
|
|
4331
|
+
const rawText = data.content?.[0]?.text ?? "";
|
|
4332
|
+
const text = ("{" + rawText).replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4075
4333
|
const parsed = JSON.parse(text);
|
|
4076
4334
|
const result = {
|
|
4077
4335
|
pass: parsed.pass === true,
|
|
@@ -4084,7 +4342,8 @@ function createModerationClient(options) {
|
|
|
4084
4342
|
}
|
|
4085
4343
|
cache.set(hash, result);
|
|
4086
4344
|
return result;
|
|
4087
|
-
} catch {
|
|
4345
|
+
} catch (err2) {
|
|
4346
|
+
console.error("[moderation] Unexpected error:", err2);
|
|
4088
4347
|
return { pass: false, reason: "Content moderation unavailable. Please try again later." };
|
|
4089
4348
|
}
|
|
4090
4349
|
}
|
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"
|