@sentry/junior-memory 0.134.0 → 0.136.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/api.d.ts +18 -0
- package/dist/index.js +46 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/api.ts +4 -0
- package/src/ranking.ts +16 -2
- package/src/store.ts +60 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.136.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"commander": "^14.0.3",
|
|
28
28
|
"drizzle-orm": "^0.45.2",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@sentry/junior-plugin-api": "0.
|
|
30
|
+
"@sentry/junior-plugin-api": "0.136.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.1",
|
package/src/api.ts
CHANGED
|
@@ -27,6 +27,8 @@ export const memoryApiSchema = z
|
|
|
27
27
|
id: z.string().min(1),
|
|
28
28
|
kind: z.enum(["preference", "procedure", "knowledge"]),
|
|
29
29
|
observedAt: z.iso.datetime(),
|
|
30
|
+
origin: z.enum(["automatic", "explicit", "other"]),
|
|
31
|
+
sourcePlatform: z.enum(["local", "slack"]),
|
|
30
32
|
visibility: z.enum(["private", "public"]),
|
|
31
33
|
})
|
|
32
34
|
.strict();
|
|
@@ -118,6 +120,8 @@ function apiMemory(
|
|
|
118
120
|
id: memory.id,
|
|
119
121
|
kind: memory.kind,
|
|
120
122
|
observedAt: new Date(memory.observedAtMs).toISOString(),
|
|
123
|
+
origin: memory.origin,
|
|
124
|
+
sourcePlatform: memory.sourcePlatform,
|
|
121
125
|
visibility: memory.visibility,
|
|
122
126
|
};
|
|
123
127
|
}
|
package/src/ranking.ts
CHANGED
|
@@ -83,10 +83,15 @@ export function rankMemoryMatches(
|
|
|
83
83
|
byId.set(match.memory.id, match);
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
86
|
+
// Keep the first rank per modality. Shared legs are fused before personal
|
|
87
|
+
// probes, so a smaller personal top-k cannot overwrite a shared dense rank
|
|
88
|
+
// with an inflated top rank for the same memory.
|
|
86
89
|
byId.set(match.memory.id, {
|
|
87
90
|
...existing,
|
|
88
|
-
...(
|
|
89
|
-
|
|
91
|
+
...(!existing.lexical && match.lexical
|
|
92
|
+
? { lexical: match.lexical }
|
|
93
|
+
: {}),
|
|
94
|
+
...(!existing.vector && match.vector ? { vector: match.vector } : {}),
|
|
90
95
|
});
|
|
91
96
|
}
|
|
92
97
|
return [...byId.values()].sort((left, right) => {
|
|
@@ -94,6 +99,15 @@ export function rankMemoryMatches(
|
|
|
94
99
|
if (scoreDelta !== 0) {
|
|
95
100
|
return scoreDelta;
|
|
96
101
|
}
|
|
102
|
+
// Prefer actor preferences over workspace knowledge when RRF ties. Shared
|
|
103
|
+
// lexical legs often assign the same top rank to recent conversation noise
|
|
104
|
+
// and a personal-scope probe hit for the same common token.
|
|
105
|
+
const personalDelta =
|
|
106
|
+
Number(right.memory.scope === "personal") -
|
|
107
|
+
Number(left.memory.scope === "personal");
|
|
108
|
+
if (personalDelta !== 0) {
|
|
109
|
+
return personalDelta;
|
|
110
|
+
}
|
|
97
111
|
const channelDelta =
|
|
98
112
|
Number(currentChannel(right, options.channelPrefix)) -
|
|
99
113
|
Number(currentChannel(left, options.channelPrefix));
|
package/src/store.ts
CHANGED
|
@@ -1066,30 +1066,17 @@ async function searchVisibleLexicalMemories(args: {
|
|
|
1066
1066
|
/** Search active visible records with pgvector cosine distance. */
|
|
1067
1067
|
async function searchVisibleVectorMemories(args: {
|
|
1068
1068
|
db: MemoryDb;
|
|
1069
|
-
|
|
1069
|
+
embedding: MemoryEmbedding;
|
|
1070
1070
|
limit: number;
|
|
1071
1071
|
maxDistance?: number;
|
|
1072
1072
|
nowMs: number;
|
|
1073
|
-
query: string;
|
|
1074
1073
|
scopes: ResolvedMemoryScope[];
|
|
1075
1074
|
}): Promise<MemoryMatch[]> {
|
|
1076
|
-
if (!args.embedder) {
|
|
1077
|
-
return [];
|
|
1078
|
-
}
|
|
1079
1075
|
const predicate = activeVisiblePredicate(args);
|
|
1080
1076
|
if (!predicate) {
|
|
1081
1077
|
return [];
|
|
1082
1078
|
}
|
|
1083
|
-
const
|
|
1084
|
-
if (!query) {
|
|
1085
|
-
return [];
|
|
1086
|
-
}
|
|
1087
|
-
let embedding: Awaited<ReturnType<typeof embedOne>>;
|
|
1088
|
-
try {
|
|
1089
|
-
embedding = await embedOne(args.embedder, query);
|
|
1090
|
-
} catch {
|
|
1091
|
-
return [];
|
|
1092
|
-
}
|
|
1079
|
+
const embedding = args.embedding;
|
|
1093
1080
|
const distance = cosineDistance(
|
|
1094
1081
|
juniorMemoryEmbeddings.embedding,
|
|
1095
1082
|
embedding.vector,
|
|
@@ -1409,6 +1396,11 @@ export function createMemoryStore(
|
|
|
1409
1396
|
* vectors already hit: that drops exact/token memories and serializes the
|
|
1410
1397
|
* miss path. Each leg is a hard-capped top-k probe so Postgres work stays
|
|
1411
1398
|
* bounded even on broad queries.
|
|
1399
|
+
*
|
|
1400
|
+
* Automatic recall also runs personal-scope-only probes. Workspace
|
|
1401
|
+
* conversation memories sharing common tokens (for example "time") can fill
|
|
1402
|
+
* the shared lexical recency window before ranking, which buries older actor
|
|
1403
|
+
* preferences that explicit search still finds.
|
|
1412
1404
|
*/
|
|
1413
1405
|
async function retrieveVisibleMemories(
|
|
1414
1406
|
rawInput: SearchMemoriesInput,
|
|
@@ -1428,30 +1420,66 @@ export function createMemoryStore(
|
|
|
1428
1420
|
? SEARCH_RETRIEVAL_OVERFETCH
|
|
1429
1421
|
: RECALL_RETRIEVAL_OVERFETCH;
|
|
1430
1422
|
const candidateLimit = retrievalLegLimit(limit, overfetch);
|
|
1423
|
+
const personalScopes = scopes.filter((scope) => scope.scope === "personal");
|
|
1424
|
+
// Automatic recall only: keep a personal-scope probe so workspace noise
|
|
1425
|
+
// cannot monopolize the shared lexical recency window.
|
|
1426
|
+
const probePersonal =
|
|
1427
|
+
vectorMaxDistance !== undefined && personalScopes.length > 0;
|
|
1428
|
+
const query = normalizeRetrievalQuery(input.query);
|
|
1429
|
+
let queryEmbedding: MemoryEmbedding | undefined;
|
|
1430
|
+
if (embedder && query) {
|
|
1431
|
+
try {
|
|
1432
|
+
queryEmbedding = await embedOne(embedder, query);
|
|
1433
|
+
} catch {
|
|
1434
|
+
queryEmbedding = undefined;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
const emptyMatches = Promise.resolve([] as MemoryMatch[]);
|
|
1438
|
+
const lexicalArgs = {
|
|
1439
|
+
db,
|
|
1440
|
+
limit: candidateLimit,
|
|
1441
|
+
nowMs,
|
|
1442
|
+
query: input.query,
|
|
1443
|
+
};
|
|
1431
1444
|
// Always run both legs in parallel. Conditional lexical skip is unsafe:
|
|
1432
1445
|
// one in-threshold vector distractor can hide a stronger lexical hit.
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1446
|
+
// Embed once up front; vector probes only run when that embedding exists.
|
|
1447
|
+
const matches = await Promise.all([
|
|
1448
|
+
queryEmbedding
|
|
1449
|
+
? searchVisibleVectorMemories({
|
|
1450
|
+
db,
|
|
1451
|
+
embedding: queryEmbedding,
|
|
1452
|
+
limit: candidateLimit,
|
|
1453
|
+
...(vectorMaxDistance !== undefined
|
|
1454
|
+
? { maxDistance: vectorMaxDistance }
|
|
1455
|
+
: {}),
|
|
1456
|
+
nowMs,
|
|
1457
|
+
scopes,
|
|
1458
|
+
})
|
|
1459
|
+
: emptyMatches,
|
|
1445
1460
|
searchVisibleLexicalMemories({
|
|
1446
|
-
|
|
1447
|
-
limit: candidateLimit,
|
|
1448
|
-
nowMs,
|
|
1449
|
-
query: input.query,
|
|
1461
|
+
...lexicalArgs,
|
|
1450
1462
|
scopes,
|
|
1451
1463
|
}),
|
|
1464
|
+
queryEmbedding && probePersonal
|
|
1465
|
+
? searchVisibleVectorMemories({
|
|
1466
|
+
db,
|
|
1467
|
+
embedding: queryEmbedding,
|
|
1468
|
+
limit: candidateLimit,
|
|
1469
|
+
maxDistance: vectorMaxDistance,
|
|
1470
|
+
nowMs,
|
|
1471
|
+
scopes: personalScopes,
|
|
1472
|
+
})
|
|
1473
|
+
: emptyMatches,
|
|
1474
|
+
probePersonal
|
|
1475
|
+
? searchVisibleLexicalMemories({
|
|
1476
|
+
...lexicalArgs,
|
|
1477
|
+
scopes: personalScopes,
|
|
1478
|
+
})
|
|
1479
|
+
: emptyMatches,
|
|
1452
1480
|
]);
|
|
1453
1481
|
const channelPrefix = sourceChannelPrefix(runtimeContext);
|
|
1454
|
-
return rankMemoryMatches(
|
|
1482
|
+
return rankMemoryMatches(matches.flat(), {
|
|
1455
1483
|
nowMs,
|
|
1456
1484
|
// Slight lexical preference protects exact ids/names/timezones on ties.
|
|
1457
1485
|
...(vectorMaxDistance === undefined
|