@sentry/junior-memory 0.118.0 → 0.120.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/dist/index.js CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  desc,
14
14
  eq,
15
15
  gt,
16
- ilike,
17
16
  inArray,
18
17
  isNull,
19
18
  isNotNull,
@@ -106,6 +105,9 @@ var juniorMemoryMemories = pgTable(
106
105
  index("junior_memory_memories_expiration_idx").on(table.expiresAtMs).where(
107
106
  sql`${table.archivedAtMs} IS NULL AND ${table.expiresAtMs} IS NOT NULL`
108
107
  ),
108
+ index("junior_memory_memories_search_idx").using("gin", sql`to_tsvector('english', ${table.content})`).where(
109
+ sql`${table.archivedAtMs} IS NULL AND ${table.supersededAtMs} IS NULL AND ${table.supersededById} IS NULL`
110
+ ),
109
111
  uniqueIndex("junior_memory_memories_idempotency_idx").on(table.scope, table.scopeKey, table.idempotencyKey).where(
110
112
  sql`${table.idempotencyKey} IS NOT NULL AND ${table.archivedAtMs} IS NULL AND ${table.supersededAtMs} IS NULL AND ${table.supersededById} IS NULL`
111
113
  ),
@@ -167,6 +169,63 @@ var juniorMemoryEmbeddings = pgTable(
167
169
  ]
168
170
  );
169
171
 
172
+ // src/ranking.ts
173
+ var RECIPROCAL_RANK_FUSION_K = 60;
174
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
175
+ function reciprocalRank(rank) {
176
+ return 1 / (RECIPROCAL_RANK_FUSION_K + rank);
177
+ }
178
+ function matchScore(match) {
179
+ return (match.vector ? reciprocalRank(match.vector.rank) : 0) + (match.lexical ? reciprocalRank(match.lexical.rank) : 0);
180
+ }
181
+ function currentChannel(match, channelPrefix) {
182
+ return channelPrefix ? match.sourceKey.startsWith(channelPrefix) : false;
183
+ }
184
+ function observedAgeRank(memory, nowMs) {
185
+ const ageMs = Math.max(0, nowMs - memory.observedAtMs);
186
+ if (ageMs <= 7 * ONE_DAY_MS) {
187
+ return 3;
188
+ }
189
+ if (ageMs <= 30 * ONE_DAY_MS) {
190
+ return 2;
191
+ }
192
+ if (ageMs <= 90 * ONE_DAY_MS) {
193
+ return 1;
194
+ }
195
+ return 0;
196
+ }
197
+ function rankMemoryMatches(matches, options) {
198
+ const byId = /* @__PURE__ */ new Map();
199
+ for (const match of matches) {
200
+ const existing = byId.get(match.memory.id);
201
+ if (!existing) {
202
+ byId.set(match.memory.id, match);
203
+ continue;
204
+ }
205
+ byId.set(match.memory.id, {
206
+ ...existing,
207
+ exactIdentifier: existing.exactIdentifier || match.exactIdentifier,
208
+ ...match.lexical ? { lexical: match.lexical } : {},
209
+ ...match.vector ? { vector: match.vector } : {}
210
+ });
211
+ }
212
+ return [...byId.values()].sort((left, right) => {
213
+ const identifierDelta = Number(right.exactIdentifier) - Number(left.exactIdentifier);
214
+ if (identifierDelta !== 0) {
215
+ return identifierDelta;
216
+ }
217
+ const scoreDelta = matchScore(right) - matchScore(left);
218
+ if (scoreDelta !== 0) {
219
+ return scoreDelta;
220
+ }
221
+ const channelDelta = Number(currentChannel(right, options.channelPrefix)) - Number(currentChannel(left, options.channelPrefix));
222
+ if (channelDelta !== 0) {
223
+ return channelDelta;
224
+ }
225
+ return observedAgeRank(right.memory, options.nowMs) - observedAgeRank(left.memory, options.nowMs) || right.memory.observedAtMs - left.memory.observedAtMs || left.memory.id.localeCompare(right.memory.id);
226
+ });
227
+ }
228
+
170
229
  // src/scope.ts
171
230
  import { isPrivateSource } from "@sentry/junior-plugin-api";
172
231
  function sourceConversationKey(ctx) {
@@ -239,15 +298,11 @@ function deriveVisibleMemoryScopes(ctx) {
239
298
  var DEFAULT_LIST_LIMIT = 50;
240
299
  var DEFAULT_SEARCH_LIMIT = 10;
241
300
  var DEFAULT_EXPIRED_ARCHIVE_LIMIT = 100;
242
- var HIGH_CONFIDENCE_DUPLICATE_DISTANCE = 0.015;
243
301
  var PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT = 10;
244
302
  var PREFERENCE_ADJUDICATION_VECTOR_LIMIT = 5;
245
303
  var VECTOR_SEARCH_OVERFETCH = 4;
246
304
  var MAX_MEMORY_CONTENT_CHARS = 4e3;
247
305
  var EMBEDDING_METRIC = "cosine";
248
- var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
249
- var RELEVANCE_NEAR_TIE_DELTA = 0.01;
250
- var SOURCE_CHANNEL_BOOST = 0.05;
251
306
  var nonEmptyStringSchema2 = z2.string().min(1);
252
307
  var memoryContentSchema = z2.string().refine((content) => content.trim().length > 0, {
253
308
  message: "Memory content is required."
@@ -553,20 +608,26 @@ async function archiveExpiredMemoryBatch(args) {
553
608
  });
554
609
  return { archivedCount: archivedIds.length };
555
610
  }
556
- function searchScore(memory, terms) {
557
- const haystack = memory.content.toLowerCase();
558
- return terms.reduce(
559
- (score, term) => score + (haystack.includes(term) ? 1 : 0),
560
- 0
561
- );
562
- }
563
- function searchTerms(query) {
611
+ function structuredQueryIdentifiers(query) {
612
+ const matches = query.toLowerCase().match(
613
+ /(?:[a-z0-9][a-z0-9._-]*\/)+[a-z0-9][a-z0-9._-]*|[a-z0-9]+(?:[._][a-z0-9]+)+|[a-z0-9]+(?:-[a-z0-9]+){2,}/g
614
+ ) ?? [];
564
615
  return [
565
- ...new Set(
566
- query.toLowerCase().split(/[^a-z0-9_'-]+/).map((term) => term.trim()).filter((term) => term.length >= 2)
567
- )
616
+ ...new Set(matches.map((identifier) => identifier.replace(/\.+$/, "")))
568
617
  ];
569
618
  }
619
+ function denseRanks(values, key) {
620
+ let previous;
621
+ let rank = 0;
622
+ return values.map((value, index2) => {
623
+ const current = key(value);
624
+ if (index2 === 0 || current !== previous) {
625
+ rank = index2 + 1;
626
+ previous = current;
627
+ }
628
+ return rank;
629
+ });
630
+ }
570
631
  async function embedOne(embedder, text2) {
571
632
  const normalized = normalizeContent(text2);
572
633
  if (!normalized) {
@@ -657,38 +718,6 @@ async function findExactDuplicateMemory(args) {
657
718
  ).limit(1);
658
719
  return rows[0] ? parseMemoryRow(rows[0]) : void 0;
659
720
  }
660
- async function findVectorDuplicateMemory(args) {
661
- const distance = cosineDistance(
662
- juniorMemoryEmbeddings.embedding,
663
- args.embedding.vector
664
- );
665
- const rows = await args.db.select({
666
- contentHash: juniorMemoryEmbeddings.contentHash,
667
- distance,
668
- memory: juniorMemoryMemories
669
- }).from(juniorMemoryMemories).innerJoin(
670
- juniorMemoryEmbeddings,
671
- eq(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
672
- ).where(
673
- and(
674
- activeScopedSubjectPredicate(args),
675
- eq(juniorMemoryEmbeddings.provider, args.embedding.provider),
676
- eq(juniorMemoryEmbeddings.model, args.embedding.model),
677
- eq(juniorMemoryEmbeddings.dimensions, MEMORY_EMBEDDING_DIMENSIONS),
678
- eq(juniorMemoryEmbeddings.metric, EMBEDDING_METRIC),
679
- lte(distance, HIGH_CONFIDENCE_DUPLICATE_DISTANCE)
680
- )
681
- ).orderBy(
682
- distance,
683
- desc(juniorMemoryMemories.createdAtMs),
684
- asc(juniorMemoryMemories.id)
685
- ).limit(1);
686
- const row = rows[0];
687
- if (!row || hashEmbeddedContent(row.memory.content) !== row.contentHash) {
688
- return void 0;
689
- }
690
- return parseMemoryRow(row.memory);
691
- }
692
721
  async function rememberDuplicateIdempotency(args) {
693
722
  if (args.idempotencyKey === void 0) {
694
723
  return;
@@ -823,29 +852,55 @@ async function listVisibleMemories(args) {
823
852
  ).limit(limit);
824
853
  return rows.map(parseMemoryRow);
825
854
  }
826
- async function searchVisibleMemories(args) {
827
- const terms = searchTerms(args.query);
828
- if (terms.length === 0) {
829
- return [];
830
- }
855
+ async function searchVisibleLexicalMemories(args) {
831
856
  const predicate = activeVisiblePredicate(args);
832
857
  if (!predicate) {
833
858
  return [];
834
859
  }
835
- const rows = await args.db.select().from(juniorMemoryMemories).where(
836
- and(
837
- predicate,
838
- or(
839
- ...terms.map(
840
- (term) => ilike(juniorMemoryMemories.content, `%${term}%`)
841
- )
842
- )
860
+ const queryVector = sql2`to_tsvector('english', ${args.query})`;
861
+ const tsquery = sql2`(
862
+ SELECT COALESCE(
863
+ string_agg(quote_literal(term), ' | ')::tsquery,
864
+ ''::tsquery
843
865
  )
866
+ FROM unnest(tsvector_to_array(${queryVector})) AS query_terms(term)
867
+ )`;
868
+ const textsearch = sql2`to_tsvector('english', ${juniorMemoryMemories.content})`;
869
+ const textRank = sql2`ts_rank_cd(${textsearch}, ${tsquery})`;
870
+ const identifiers = structuredQueryIdentifiers(args.query);
871
+ const contentTokens = sql2`regexp_split_to_array(lower(${juniorMemoryMemories.content}), '[^a-z0-9._/-]+')`;
872
+ const identifierArray = identifiers.length > 0 ? sql2`ARRAY[${sql2.join(
873
+ identifiers.map((identifier) => sql2`${identifier}`),
874
+ sql2`, `
875
+ )}]::text[]` : void 0;
876
+ const exactIdentifierMatch = identifierArray !== void 0 ? sql2`EXISTS (
877
+ SELECT 1
878
+ FROM unnest(${contentTokens}) AS content_token(value)
879
+ CROSS JOIN unnest(${identifierArray}) AS query_identifier(value)
880
+ WHERE strpos(
881
+ '/' || regexp_replace(content_token.value, '\\.+$', '') || '/',
882
+ '/' || query_identifier.value || '/'
883
+ ) > 0
884
+ )` : sql2`false`;
885
+ const rows = await args.db.select({
886
+ exactIdentifierMatch,
887
+ memory: juniorMemoryMemories,
888
+ textRank
889
+ }).from(juniorMemoryMemories).where(and(predicate, sql2`${textsearch} @@ ${tsquery}`)).orderBy(
890
+ ...identifiers.length > 0 ? [desc(exactIdentifierMatch)] : [],
891
+ desc(textRank),
892
+ desc(juniorMemoryMemories.observedAtMs),
893
+ asc(juniorMemoryMemories.id)
894
+ ).limit(args.limit);
895
+ const ranks = denseRanks(
896
+ rows,
897
+ (row) => `${row.exactIdentifierMatch}:${Number(row.textRank)}`
844
898
  );
845
- return rows.map((row) => ({
846
- memory: parseMemoryRow(row),
847
- score: 0,
848
- sourceKey: row.sourceKey
899
+ return rows.map((row, index2) => ({
900
+ exactIdentifier: row.exactIdentifierMatch,
901
+ lexical: { rank: ranks[index2] },
902
+ memory: parseMemoryRow(row.memory),
903
+ sourceKey: row.memory.sourceKey
849
904
  }));
850
905
  }
851
906
  async function searchVisibleVectorMemories(args) {
@@ -886,7 +941,8 @@ async function searchVisibleVectorMemories(args) {
886
941
  desc(juniorMemoryMemories.createdAtMs),
887
942
  asc(juniorMemoryMemories.id)
888
943
  ).limit(args.limit);
889
- return rows.flatMap((row) => {
944
+ const ranks = denseRanks(rows, (row) => Number(row.distance));
945
+ return rows.flatMap((row, index2) => {
890
946
  const distanceValue = Number(row.distance);
891
947
  if (row.distance === null || !Number.isFinite(distanceValue) || hashEmbeddedContent(row.memory.content) !== row.contentHash) {
892
948
  return [];
@@ -896,58 +952,16 @@ async function searchVisibleVectorMemories(args) {
896
952
  }
897
953
  return [
898
954
  {
955
+ exactIdentifier: false,
899
956
  memory: parseMemoryRow(row.memory),
900
- score: 1 / (1 + Math.max(0, distanceValue)),
901
- sourceKey: row.memory.sourceKey
957
+ sourceKey: row.memory.sourceKey,
958
+ vector: {
959
+ rank: ranks[index2]
960
+ }
902
961
  }
903
962
  ];
904
963
  });
905
964
  }
906
- function sourceBoost(candidate, currentChannelPrefix) {
907
- if (!currentChannelPrefix) {
908
- return 0;
909
- }
910
- return candidate.sourceKey.startsWith(currentChannelPrefix) ? SOURCE_CHANNEL_BOOST : 0;
911
- }
912
- function observedAgeBoost(memory, nowMs) {
913
- const ageMs = Math.max(0, nowMs - memory.observedAtMs);
914
- if (ageMs <= 7 * ONE_DAY_MS) {
915
- return 0.15;
916
- }
917
- if (ageMs <= 30 * ONE_DAY_MS) {
918
- return 0.1;
919
- }
920
- if (ageMs <= 90 * ONE_DAY_MS) {
921
- return 0.05;
922
- }
923
- return 0;
924
- }
925
- function mergeSearchCandidates(candidates, nowMs, currentChannelKey) {
926
- const byId = /* @__PURE__ */ new Map();
927
- for (const candidate of candidates) {
928
- const existing = byId.get(candidate.memory.id);
929
- if (existing) {
930
- existing.score += candidate.score;
931
- continue;
932
- }
933
- byId.set(candidate.memory.id, {
934
- memory: candidate.memory,
935
- score: candidate.score,
936
- sourceKey: candidate.sourceKey
937
- });
938
- }
939
- return [...byId.values()].sort((left, right) => {
940
- const scoreDelta = right.score - left.score;
941
- if (Math.abs(scoreDelta) > RELEVANCE_NEAR_TIE_DELTA) {
942
- return scoreDelta;
943
- }
944
- const sourceDelta = sourceBoost(right, currentChannelKey) - sourceBoost(left, currentChannelKey);
945
- if (sourceDelta !== 0) {
946
- return sourceDelta;
947
- }
948
- return observedAgeBoost(right.memory, nowMs) - observedAgeBoost(left.memory, nowMs) || right.memory.observedAtMs - left.memory.observedAtMs || left.memory.id.localeCompare(right.memory.id);
949
- }).map((candidate) => candidate.memory);
950
- }
951
965
  function createMemoryStore(db, context, options = {}) {
952
966
  const runtimeContext2 = memoryRuntimeContextSchema.parse(context);
953
967
  const parsedOptions = memoryStoreOptionsSchema.parse({ now: options.now });
@@ -1044,24 +1058,6 @@ function createMemoryStore(db, context, options = {}) {
1044
1058
  candidateEmbedding = void 0;
1045
1059
  }
1046
1060
  }
1047
- const vectorDuplicate = candidateEmbedding ? await findVectorDuplicateMemory({
1048
- db,
1049
- embedding: candidateEmbedding,
1050
- kind: input.kind,
1051
- nowMs,
1052
- scope,
1053
- subject
1054
- }) : void 0;
1055
- if (vectorDuplicate) {
1056
- return await reuseDuplicateMemory({
1057
- content,
1058
- duplicate: vectorDuplicate,
1059
- idempotencyKey: input.idempotencyKey,
1060
- nowMs,
1061
- scope,
1062
- subject
1063
- });
1064
- }
1065
1061
  let supersededIds = [];
1066
1062
  if (scopeKind === "personal" && input.kind === "preference" && supersessionDecider && (input.expiresAtMs === void 0 || input.expiresAtMs > nowMs)) {
1067
1063
  const preferenceCandidates = await listPreferenceAdjudicationCandidates({
@@ -1169,6 +1165,41 @@ function createMemoryStore(db, context, options = {}) {
1169
1165
  });
1170
1166
  return { created: false, memory: idempotent };
1171
1167
  }
1168
+ async function retrieveVisibleMemories(rawInput, vectorMaxDistance) {
1169
+ const input = searchMemoriesInputSchema.parse(rawInput);
1170
+ const nowMs = getNowMs();
1171
+ const scopes = deriveVisibleMemoryScopes(runtimeContext2);
1172
+ await archiveExpiredMemoryBatch({
1173
+ db,
1174
+ nowMs,
1175
+ scopes
1176
+ });
1177
+ const limit = boundedLimit(input.limit, DEFAULT_SEARCH_LIMIT);
1178
+ const candidateLimit = limit * VECTOR_SEARCH_OVERFETCH;
1179
+ const [vectorMatches, lexicalMatches] = await Promise.all([
1180
+ searchVisibleVectorMemories({
1181
+ db,
1182
+ embedder,
1183
+ limit: candidateLimit,
1184
+ ...vectorMaxDistance !== void 0 ? { maxDistance: vectorMaxDistance } : {},
1185
+ nowMs,
1186
+ query: input.query,
1187
+ scopes
1188
+ }),
1189
+ searchVisibleLexicalMemories({
1190
+ db,
1191
+ limit: candidateLimit,
1192
+ nowMs,
1193
+ query: input.query,
1194
+ scopes
1195
+ })
1196
+ ]);
1197
+ const channelPrefix = sourceChannelPrefix(runtimeContext2);
1198
+ return rankMemoryMatches([...vectorMatches, ...lexicalMatches], {
1199
+ nowMs,
1200
+ ...channelPrefix ? { channelPrefix } : {}
1201
+ }).slice(0, limit).map(({ memory }) => memory);
1202
+ }
1172
1203
  return {
1173
1204
  async archiveExpiredMemories(input) {
1174
1205
  return await archiveExpiredVisibleMemories(input, getNowMs());
@@ -1211,41 +1242,11 @@ function createMemoryStore(db, context, options = {}) {
1211
1242
  scopes
1212
1243
  });
1213
1244
  },
1245
+ async recallMemories(input) {
1246
+ return await retrieveVisibleMemories(input, maxVectorDistance);
1247
+ },
1214
1248
  async searchMemories(input) {
1215
- input = searchMemoriesInputSchema.parse(input);
1216
- const nowMs = getNowMs();
1217
- const scopes = deriveVisibleMemoryScopes(runtimeContext2);
1218
- await archiveExpiredMemoryBatch({
1219
- db,
1220
- nowMs,
1221
- scopes
1222
- });
1223
- const limit = boundedLimit(input.limit, DEFAULT_SEARCH_LIMIT);
1224
- const vectorCandidates = await searchVisibleVectorMemories({
1225
- db,
1226
- embedder,
1227
- limit: limit * VECTOR_SEARCH_OVERFETCH,
1228
- ...maxVectorDistance !== void 0 ? { maxDistance: maxVectorDistance } : {},
1229
- nowMs,
1230
- query: input.query,
1231
- scopes
1232
- });
1233
- const candidates = await searchVisibleMemories({
1234
- db,
1235
- nowMs,
1236
- query: input.query,
1237
- scopes
1238
- });
1239
- const terms = searchTerms(input.query);
1240
- const lexicalCandidates = candidates.map((candidate) => ({
1241
- ...candidate,
1242
- score: searchScore(candidate.memory, terms)
1243
- })).filter((item) => item.score > 0);
1244
- return mergeSearchCandidates(
1245
- [...vectorCandidates, ...lexicalCandidates],
1246
- nowMs,
1247
- sourceChannelPrefix(runtimeContext2)
1248
- ).slice(0, limit);
1249
+ return await retrieveVisibleMemories(input, void 0);
1249
1250
  },
1250
1251
  async archiveMemory(input) {
1251
1252
  input = archiveMemoryInputSchema.parse(input);
@@ -1294,6 +1295,19 @@ var memoryRejectReasonSchema = z3.enum([
1294
1295
  "assistant_or_system_detail",
1295
1296
  "unsupported_scope"
1296
1297
  ]);
1298
+ var memoryRecallCandidateSchema = z3.object({
1299
+ content: z3.string().min(1),
1300
+ id: z3.string().min(1)
1301
+ }).strict();
1302
+ var memoryRecallInputSchema = z3.object({
1303
+ candidates: z3.array(memoryRecallCandidateSchema).min(1).max(20),
1304
+ userRequest: z3.string().min(1)
1305
+ }).strict();
1306
+ var memoryRecallDecisionSchema = z3.object({
1307
+ relevantIds: z3.array(z3.string().min(1)).max(20).describe(
1308
+ "Candidate ids whose memories directly help with the current request, ordered by relevance."
1309
+ )
1310
+ }).strict();
1297
1311
  var createMemoryRequestSchema = z3.object({
1298
1312
  content: z3.string().min(1),
1299
1313
  expiresAtMs: z3.number().finite().optional(),
@@ -1399,6 +1413,13 @@ var MEMORY_EXTRACTION_SYSTEM = [
1399
1413
  "Reject secrets, credentials, private or sensitive personal details, gossip, speculative claims about other people, assistant/system implementation details, vague references, and low-durability chatter.",
1400
1414
  "If no public, durable, self-contained memory remains after rewriting, return an empty memories array."
1401
1415
  ].join("\n");
1416
+ var MEMORY_RECALL_SYSTEM = [
1417
+ "You are Junior's memory recall relevance agent.",
1418
+ "Select only memories that would directly help answer the user's current request.",
1419
+ "Reject memories that merely share a company, product family, repository vocabulary, programming language, or general engineering context.",
1420
+ "Prefer specific matches on the exact repository, workflow, command, test, CI setup, project, or user preference being asked about.",
1421
+ "An empty relevantIds array is correct when no candidate is directly helpful."
1422
+ ].join("\n");
1402
1423
  var MEMORY_PREFERENCE_ADJUDICATION_SYSTEM = [
1403
1424
  "You are Junior's memory preference adjudication agent.",
1404
1425
  "Classify how one new actor preference relates to existing active actor preferences.",
@@ -1598,6 +1619,21 @@ function sessionExtractionPrompt(request) {
1598
1619
  "</memory-extraction-input>"
1599
1620
  ].join("\n");
1600
1621
  }
1622
+ function recallRelevancePrompt(request) {
1623
+ return [
1624
+ "<memory-recall-input>",
1625
+ "<user-request>",
1626
+ escapeXml(request.userRequest),
1627
+ "</user-request>",
1628
+ "",
1629
+ "<candidate-memories>",
1630
+ escapeXml(JSON.stringify(request.candidates)),
1631
+ "</candidate-memories>",
1632
+ "",
1633
+ "Return only ids from candidate-memories. Preserve the most relevant candidates first.",
1634
+ "</memory-recall-input>"
1635
+ ].join("\n");
1636
+ }
1601
1637
  function preferenceAdjudicationPrompt(request) {
1602
1638
  return [
1603
1639
  "<memory-preference-adjudication-input>",
@@ -1630,6 +1666,20 @@ function preferenceAdjudicationPrompt(request) {
1630
1666
  }
1631
1667
  function createMemoryAgent(model) {
1632
1668
  return {
1669
+ async selectRelevantMemories(rawRequest) {
1670
+ const request = memoryRecallInputSchema.parse(rawRequest);
1671
+ const result = await model.completeObject({
1672
+ schema: memoryRecallDecisionSchema,
1673
+ system: MEMORY_RECALL_SYSTEM,
1674
+ prompt: recallRelevancePrompt(request),
1675
+ maxTokens: 400
1676
+ });
1677
+ const decision = memoryRecallDecisionSchema.parse(result.object);
1678
+ const candidateIds = new Set(request.candidates.map(({ id }) => id));
1679
+ return [...new Set(decision.relevantIds)].filter(
1680
+ (id) => candidateIds.has(id)
1681
+ );
1682
+ },
1633
1683
  async adjudicateSupersession(rawRequest) {
1634
1684
  const request = memorySupersessionInputSchema.parse(rawRequest);
1635
1685
  const result = await model.completeObject({
@@ -1698,9 +1748,317 @@ function parseCreateMemoryRequest(request) {
1698
1748
  return createMemoryRequestSchema.parse(request);
1699
1749
  }
1700
1750
 
1751
+ // src/api.ts
1752
+ import { z as z6 } from "zod";
1753
+ import {
1754
+ pluginApiRouteRequestContextSchema
1755
+ } from "@sentry/junior-plugin-api";
1756
+
1757
+ // src/personal.ts
1758
+ import { z as z5 } from "zod";
1759
+
1760
+ // src/personal-store.ts
1761
+ import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2, ilike, lt, or as or2, sql as sql3 } from "drizzle-orm";
1762
+ import { z as z4 } from "zod";
1763
+ var nonEmptyStringSchema3 = z4.string().min(1);
1764
+ var personalMemoryCursorSchema = z4.object({
1765
+ createdAtMs: z4.number().finite(),
1766
+ id: nonEmptyStringSchema3
1767
+ }).strict();
1768
+ var personalMemoryPageInputSchema = z4.object({
1769
+ cursor: personalMemoryCursorSchema.optional(),
1770
+ limit: z4.number().int().min(1).max(50),
1771
+ query: z4.string().max(200).optional()
1772
+ }).strict();
1773
+ var PersonalMemoryNotFoundError = class extends Error {
1774
+ constructor() {
1775
+ super("Memory was not found for the authenticated viewer.");
1776
+ this.name = "PersonalMemoryNotFoundError";
1777
+ }
1778
+ };
1779
+ function personalScopes(runtimeContexts) {
1780
+ return [
1781
+ ...new Map(
1782
+ runtimeContexts.map((context) => {
1783
+ const scope = deriveMemoryScope(context, "personal");
1784
+ return [`${scope.scope}:${scope.scopeKey}`, scope];
1785
+ })
1786
+ ).values()
1787
+ ];
1788
+ }
1789
+ function searchTerms(query) {
1790
+ return [
1791
+ ...new Set(
1792
+ query.toLowerCase().split(/[^a-z0-9_'-]+/).map((term) => term.trim()).filter((term) => term.length >= 2)
1793
+ )
1794
+ ];
1795
+ }
1796
+ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1797
+ const scopes = personalScopes(runtimeContexts);
1798
+ const getNowMs = () => options.now?.() ?? Date.now();
1799
+ return {
1800
+ async archive(id) {
1801
+ const memoryId = nonEmptyStringSchema3.parse(id);
1802
+ const nowMs = getNowMs();
1803
+ const predicate = activeVisiblePredicate({ nowMs, scopes });
1804
+ if (!predicate) {
1805
+ throw new PersonalMemoryNotFoundError();
1806
+ }
1807
+ const updated = await db.update(juniorMemoryMemories).set({
1808
+ archivedAtMs: nowMs,
1809
+ archiveReason: "user_removed"
1810
+ }).where(and2(predicate, eq2(juniorMemoryMemories.id, memoryId))).returning();
1811
+ if (!updated[0]) {
1812
+ throw new PersonalMemoryNotFoundError();
1813
+ }
1814
+ await db.delete(juniorMemoryEmbeddings).where(eq2(juniorMemoryEmbeddings.memoryId, memoryId));
1815
+ return parseMemoryRow(updated[0]);
1816
+ },
1817
+ async get(id) {
1818
+ const memoryId = nonEmptyStringSchema3.parse(id);
1819
+ const nowMs = getNowMs();
1820
+ const predicate = activeVisiblePredicate({ nowMs, scopes });
1821
+ if (!predicate) {
1822
+ throw new PersonalMemoryNotFoundError();
1823
+ }
1824
+ const rows = await db.select().from(juniorMemoryMemories).where(and2(predicate, eq2(juniorMemoryMemories.id, memoryId))).limit(1);
1825
+ if (!rows[0]) {
1826
+ throw new PersonalMemoryNotFoundError();
1827
+ }
1828
+ return parseMemoryRow(rows[0]);
1829
+ },
1830
+ async list(input) {
1831
+ input = personalMemoryPageInputSchema.parse(input);
1832
+ const nowMs = getNowMs();
1833
+ await archiveExpiredMemoryBatch({ db, nowMs, scopes });
1834
+ const active = activeVisiblePredicate({ nowMs, scopes });
1835
+ if (!active) {
1836
+ return { memories: [] };
1837
+ }
1838
+ const cursor = input.cursor ? or2(
1839
+ lt(juniorMemoryMemories.createdAtMs, input.cursor.createdAtMs),
1840
+ and2(
1841
+ eq2(juniorMemoryMemories.createdAtMs, input.cursor.createdAtMs),
1842
+ gt2(juniorMemoryMemories.id, input.cursor.id)
1843
+ )
1844
+ ) : void 0;
1845
+ const terms = input.query ? searchTerms(input.query) : [];
1846
+ const search = input.query === void 0 ? void 0 : terms.length === 0 ? sql3`false` : or2(
1847
+ ...terms.map(
1848
+ (term) => ilike(juniorMemoryMemories.content, `%${term}%`)
1849
+ )
1850
+ );
1851
+ const rows = await db.select().from(juniorMemoryMemories).where(and2(active, cursor, search)).orderBy(
1852
+ desc2(juniorMemoryMemories.createdAtMs),
1853
+ asc2(juniorMemoryMemories.id)
1854
+ ).limit(input.limit + 1);
1855
+ const hasNextPage = rows.length > input.limit;
1856
+ const memories = rows.slice(0, input.limit).map(parseMemoryRow);
1857
+ const last = memories.at(-1);
1858
+ return {
1859
+ memories,
1860
+ ...hasNextPage && last ? {
1861
+ nextCursor: {
1862
+ createdAtMs: last.createdAtMs,
1863
+ id: last.id
1864
+ }
1865
+ } : {}
1866
+ };
1867
+ }
1868
+ };
1869
+ }
1870
+
1871
+ // src/personal.ts
1872
+ var cursorSchema = z5.object({
1873
+ createdAtMs: z5.number().finite(),
1874
+ id: z5.string().min(1),
1875
+ query: z5.string().max(200).optional(),
1876
+ version: z5.literal(1)
1877
+ }).strict();
1878
+ var InvalidMemoryCursorError = class extends Error {
1879
+ constructor() {
1880
+ super("Memory cursor is invalid.");
1881
+ this.name = "InvalidMemoryCursorError";
1882
+ }
1883
+ };
1884
+ function runtimeContext(actor) {
1885
+ if (actor.platform === "slack") {
1886
+ return {
1887
+ actor,
1888
+ source: {
1889
+ platform: "slack",
1890
+ type: "priv",
1891
+ teamId: actor.teamId,
1892
+ channelId: "DDASHBOARD"
1893
+ }
1894
+ };
1895
+ }
1896
+ return {
1897
+ actor,
1898
+ source: {
1899
+ platform: "local",
1900
+ type: "priv",
1901
+ conversationId: "local:dashboard:memories"
1902
+ }
1903
+ };
1904
+ }
1905
+ function decodeCursor(value, query) {
1906
+ if (!value) return void 0;
1907
+ try {
1908
+ const parsed = cursorSchema.parse(
1909
+ JSON.parse(Buffer.from(value, "base64url").toString("utf8"))
1910
+ );
1911
+ if (parsed.query !== query) {
1912
+ throw new InvalidMemoryCursorError();
1913
+ }
1914
+ return { createdAtMs: parsed.createdAtMs, id: parsed.id };
1915
+ } catch {
1916
+ throw new InvalidMemoryCursorError();
1917
+ }
1918
+ }
1919
+ function encodeCursor(cursor, query) {
1920
+ return Buffer.from(
1921
+ JSON.stringify({ ...cursor, ...query ? { query } : {}, version: 1 }),
1922
+ "utf8"
1923
+ ).toString("base64url");
1924
+ }
1925
+ function createViewerMemories(db, actors) {
1926
+ const collection = createPersonalMemoryCollection(
1927
+ db,
1928
+ actors.map(runtimeContext)
1929
+ );
1930
+ return {
1931
+ async archive(id) {
1932
+ return await collection.archive(id);
1933
+ },
1934
+ async get(id) {
1935
+ return await collection.get(id);
1936
+ },
1937
+ async list(input) {
1938
+ const query = input.query?.trim() || void 0;
1939
+ const page = await collection.list({
1940
+ cursor: decodeCursor(input.cursor, query),
1941
+ limit: input.limit,
1942
+ ...query ? { query } : {}
1943
+ });
1944
+ return {
1945
+ memories: page.memories,
1946
+ ...page.nextCursor ? { nextCursor: encodeCursor(page.nextCursor, query) } : {}
1947
+ };
1948
+ }
1949
+ };
1950
+ }
1951
+
1952
+ // src/api.ts
1953
+ var memoryApiSchema = z6.object({
1954
+ content: z6.string().min(1),
1955
+ createdAt: z6.iso.datetime(),
1956
+ expiresAt: z6.iso.datetime().optional(),
1957
+ id: z6.string().min(1),
1958
+ kind: z6.enum(["preference", "procedure", "knowledge"]),
1959
+ observedAt: z6.iso.datetime()
1960
+ }).strict();
1961
+ var memoryListResponseSchema = z6.object({
1962
+ memories: z6.array(memoryApiSchema),
1963
+ nextCursor: z6.string().min(1).optional()
1964
+ }).strict();
1965
+ var memoryListQuerySchema = z6.object({
1966
+ cursor: z6.string().min(1).max(1e3).optional(),
1967
+ limit: z6.coerce.number().int().min(1).max(50).default(25),
1968
+ q: z6.string().trim().max(200).optional()
1969
+ }).strict();
1970
+ function json(body, status = 200) {
1971
+ return Response.json(body, {
1972
+ headers: { "cache-control": "no-store" },
1973
+ status
1974
+ });
1975
+ }
1976
+ function apiMemory(memory) {
1977
+ return {
1978
+ content: memory.content,
1979
+ createdAt: new Date(memory.createdAtMs).toISOString(),
1980
+ ...memory.expiresAtMs !== void 0 ? { expiresAt: new Date(memory.expiresAtMs).toISOString() } : {},
1981
+ id: memory.id,
1982
+ kind: memory.kind,
1983
+ observedAt: new Date(memory.observedAtMs).toISOString()
1984
+ };
1985
+ }
1986
+ function viewerEmail(context) {
1987
+ const parsed = pluginApiRouteRequestContextSchema.safeParse(context);
1988
+ if (!parsed.success || parsed.data.auth.user.emailVerified !== true) {
1989
+ return void 0;
1990
+ }
1991
+ return parsed.data.auth.user.email?.trim().toLowerCase() || void 0;
1992
+ }
1993
+ function createMemoryApi(options) {
1994
+ return {
1995
+ async fetch(request, context) {
1996
+ const email = viewerEmail(context);
1997
+ if (!email) {
1998
+ return json({ error: "Authentication required." }, 401);
1999
+ }
2000
+ const url = new URL(request.url);
2001
+ const memoryPath = /^\/memories\/([^/]+)$/.exec(url.pathname);
2002
+ const isCollection = url.pathname === "/memories";
2003
+ if (!isCollection && !memoryPath) {
2004
+ return json({ error: "Not found." }, 404);
2005
+ }
2006
+ const actors = await options.actors(email);
2007
+ const memories = createViewerMemories(options.db, actors);
2008
+ try {
2009
+ if (isCollection && (request.method === "GET" || request.method === "HEAD")) {
2010
+ const query = memoryListQuerySchema.parse({
2011
+ cursor: url.searchParams.get("cursor") ?? void 0,
2012
+ limit: url.searchParams.get("limit") ?? void 0,
2013
+ q: url.searchParams.get("q") ?? void 0
2014
+ });
2015
+ const page = await memories.list({
2016
+ cursor: query.cursor,
2017
+ limit: query.limit,
2018
+ ...query.q ? { query: query.q } : {}
2019
+ });
2020
+ const body = memoryListResponseSchema.parse({
2021
+ memories: page.memories.map(apiMemory),
2022
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
2023
+ });
2024
+ return request.method === "HEAD" ? new Response(null, {
2025
+ headers: { "cache-control": "no-store" },
2026
+ status: 200
2027
+ }) : json(body);
2028
+ }
2029
+ if (memoryPath && (request.method === "GET" || request.method === "HEAD")) {
2030
+ const memory = memoryApiSchema.parse(
2031
+ apiMemory(await memories.get(decodeURIComponent(memoryPath[1])))
2032
+ );
2033
+ return request.method === "HEAD" ? new Response(null, {
2034
+ headers: { "cache-control": "no-store" },
2035
+ status: 200
2036
+ }) : json(memory);
2037
+ }
2038
+ if (memoryPath && request.method === "DELETE") {
2039
+ await memories.archive(decodeURIComponent(memoryPath[1]));
2040
+ return new Response(null, {
2041
+ headers: { "cache-control": "no-store" },
2042
+ status: 204
2043
+ });
2044
+ }
2045
+ } catch (error) {
2046
+ if (error instanceof z6.ZodError || error instanceof InvalidMemoryCursorError) {
2047
+ return json({ error: "Invalid memory request." }, 400);
2048
+ }
2049
+ if (error instanceof PersonalMemoryNotFoundError) {
2050
+ return json({ error: error.message }, 404);
2051
+ }
2052
+ throw error;
2053
+ }
2054
+ return json({ error: "Method not allowed." }, 405);
2055
+ }
2056
+ };
2057
+ }
2058
+
1701
2059
  // src/cli/search.ts
1702
2060
  import { InvalidArgumentError, Option } from "commander";
1703
- import { and as and2, desc as desc2, eq as eq2, gt as gt2, ilike as ilike2, isNull as isNull2, or as or2 } from "drizzle-orm";
2061
+ import { and as and3, desc as desc3, eq as eq3, gt as gt3, ilike as ilike2, isNull as isNull2, or as or3 } from "drizzle-orm";
1704
2062
 
1705
2063
  // src/cli/format.ts
1706
2064
  function formatDate(ms) {
@@ -1742,13 +2100,13 @@ async function runSearch(ctx, queryParts, options) {
1742
2100
  )
1743
2101
  ];
1744
2102
  const db = ctx.db;
1745
- const activeExpirationPredicate = or2(
2103
+ const activeExpirationPredicate = or3(
1746
2104
  isNull2(juniorMemoryMemories.expiresAtMs),
1747
- gt2(juniorMemoryMemories.expiresAtMs, nowMs)
2105
+ gt3(juniorMemoryMemories.expiresAtMs, nowMs)
1748
2106
  );
1749
2107
  const predicates = [
1750
- eq2(juniorMemoryMemories.scope, options.scope),
1751
- eq2(juniorMemoryMemories.scopeKey, options.scopeKey),
2108
+ eq3(juniorMemoryMemories.scope, options.scope),
2109
+ eq3(juniorMemoryMemories.scopeKey, options.scopeKey),
1752
2110
  isNull2(juniorMemoryMemories.archivedAtMs),
1753
2111
  isNull2(juniorMemoryMemories.supersededAtMs),
1754
2112
  isNull2(juniorMemoryMemories.supersededById)
@@ -1757,14 +2115,14 @@ async function runSearch(ctx, queryParts, options) {
1757
2115
  predicates.push(activeExpirationPredicate);
1758
2116
  }
1759
2117
  if (terms.length > 0) {
1760
- const termPredicate = or2(
2118
+ const termPredicate = or3(
1761
2119
  ...terms.map((term) => ilike2(juniorMemoryMemories.content, `%${term}%`))
1762
2120
  );
1763
2121
  if (termPredicate) {
1764
2122
  predicates.push(termPredicate);
1765
2123
  }
1766
2124
  }
1767
- const rows = await db.select().from(juniorMemoryMemories).where(and2(...predicates)).orderBy(desc2(juniorMemoryMemories.createdAtMs)).limit(options.limit);
2125
+ const rows = await db.select().from(juniorMemoryMemories).where(and3(...predicates)).orderBy(desc3(juniorMemoryMemories.createdAtMs)).limit(options.limit);
1768
2126
  if (rows.length === 0) {
1769
2127
  await ctx.io.writeOutput("No memories matched.\n");
1770
2128
  return 0;
@@ -1794,10 +2152,10 @@ function configureMemorySearchCommand(parent, junior) {
1794
2152
  }
1795
2153
 
1796
2154
  // src/cli/show.ts
1797
- import { eq as eq3 } from "drizzle-orm";
2155
+ import { eq as eq4 } from "drizzle-orm";
1798
2156
  async function runShow(ctx, id) {
1799
2157
  const db = ctx.db;
1800
- const rows = await db.select().from(juniorMemoryMemories).where(eq3(juniorMemoryMemories.id, id)).limit(1);
2158
+ const rows = await db.select().from(juniorMemoryMemories).where(eq4(juniorMemoryMemories.id, id)).limit(1);
1801
2159
  if (!rows[0]) {
1802
2160
  await ctx.io.writeError(`Memory not found: ${id}
1803
2161
  `);
@@ -1836,7 +2194,7 @@ import {
1836
2194
  PluginToolInputError,
1837
2195
  pluginToolResultSchema
1838
2196
  } from "@sentry/junior-plugin-api";
1839
- import { z as z4 } from "zod";
2197
+ import { z as z7 } from "zod";
1840
2198
  var MAX_TOOL_CONTENT_CHARS = 4e3;
1841
2199
  var DEFAULT_RESULT_LIMIT = 20;
1842
2200
  var DEFAULT_SEARCH_LIMIT2 = 10;
@@ -1963,23 +2321,23 @@ function requireMemoryContent(value) {
1963
2321
  }
1964
2322
  return value;
1965
2323
  }
1966
- var createMemoryInputSchema2 = z4.object({
1967
- content: z4.string().min(1).max(MAX_TOOL_CONTENT_CHARS).describe(
2324
+ var createMemoryInputSchema2 = z7.object({
2325
+ content: z7.string().min(1).max(MAX_TOOL_CONTENT_CHARS).describe(
1968
2326
  "Self-contained public/shareable memory candidate. Include the subject in natural language when it matters; do not rely on surrounding chat context."
1969
2327
  ),
1970
- expires_at: z4.string().min(1).describe(
2328
+ expires_at: z7.string().min(1).describe(
1971
2329
  'Expiration selector. Omit or use "never" when the memory should not expire, or use an exact ISO timestamp such as "2027-06-21T00:00:00Z".'
1972
2330
  ).optional()
1973
2331
  }).strict();
1974
- var removeMemoryInputSchema = z4.object({
1975
- id: z4.string().min(1).describe("Memory id or unambiguous short id prefix to remove.")
2332
+ var removeMemoryInputSchema = z7.object({
2333
+ id: z7.string().min(1).describe("Memory id or unambiguous short id prefix to remove.")
1976
2334
  }).strict();
1977
- var listMemoriesInputSchema2 = z4.object({
1978
- limit: z4.number().min(1).max(50).describe("Maximum number of visible memories to return.").optional()
2335
+ var listMemoriesInputSchema2 = z7.object({
2336
+ limit: z7.number().min(1).max(50).describe("Maximum number of visible memories to return.").optional()
1979
2337
  }).strict();
1980
- var searchMemoriesInputSchema2 = z4.object({
1981
- query: z4.string().min(1).describe("Search query for visible memory content."),
1982
- limit: z4.number().min(1).max(50).describe("Maximum number of matching memories to return.").optional()
2338
+ var searchMemoriesInputSchema2 = z7.object({
2339
+ query: z7.string().min(1).describe("Search query for visible memory content."),
2340
+ limit: z7.number().min(1).max(50).describe("Maximum number of matching memories to return.").optional()
1983
2341
  }).strict();
1984
2342
  var memoryToolProjectionSchema = Type.Object(
1985
2343
  {
@@ -1991,44 +2349,44 @@ var memoryToolProjectionSchema = Type.Object(
1991
2349
  },
1992
2350
  { additionalProperties: false }
1993
2351
  );
1994
- var memoryProjectionOutputSchema = z4.object({
1995
- id: z4.string(),
1996
- content: z4.string(),
1997
- createdAtMs: z4.number(),
1998
- observedAtMs: z4.number(),
1999
- expiresAtMs: z4.number().optional()
2352
+ var memoryProjectionOutputSchema = z7.object({
2353
+ id: z7.string(),
2354
+ content: z7.string(),
2355
+ createdAtMs: z7.number(),
2356
+ observedAtMs: z7.number(),
2357
+ expiresAtMs: z7.number().optional()
2000
2358
  });
2001
- var memoryCreateDataOutputSchema = z4.object({
2002
- created: z4.boolean(),
2359
+ var memoryCreateDataOutputSchema = z7.object({
2360
+ created: z7.boolean(),
2003
2361
  memory: memoryProjectionOutputSchema
2004
2362
  });
2005
- var memorySingleDataOutputSchema = z4.object({
2363
+ var memorySingleDataOutputSchema = z7.object({
2006
2364
  memory: memoryProjectionOutputSchema
2007
2365
  });
2008
- var memoryManyDataOutputSchema = z4.object({
2009
- memories: z4.array(memoryProjectionOutputSchema)
2366
+ var memoryManyDataOutputSchema = z7.object({
2367
+ memories: z7.array(memoryProjectionOutputSchema)
2010
2368
  });
2011
2369
  var memoryCreateOutputSchema = pluginToolResultSchema.extend({
2012
- ok: z4.literal(true),
2013
- status: z4.literal("success"),
2014
- target: z4.string(),
2370
+ ok: z7.literal(true),
2371
+ status: z7.literal("success"),
2372
+ target: z7.string(),
2015
2373
  data: memoryCreateDataOutputSchema,
2016
- created: z4.boolean(),
2374
+ created: z7.boolean(),
2017
2375
  memory: memoryProjectionOutputSchema
2018
2376
  });
2019
2377
  var memorySingleOutputSchema = pluginToolResultSchema.extend({
2020
- ok: z4.literal(true),
2021
- status: z4.literal("success"),
2022
- target: z4.string(),
2378
+ ok: z7.literal(true),
2379
+ status: z7.literal("success"),
2380
+ target: z7.string(),
2023
2381
  data: memorySingleDataOutputSchema,
2024
2382
  memory: memoryProjectionOutputSchema
2025
2383
  });
2026
2384
  var memoryManyOutputSchema = pluginToolResultSchema.extend({
2027
- ok: z4.literal(true),
2028
- status: z4.literal("success"),
2029
- target: z4.string(),
2385
+ ok: z7.literal(true),
2386
+ status: z7.literal("success"),
2387
+ target: z7.string(),
2030
2388
  data: memoryManyDataOutputSchema,
2031
- memories: z4.array(memoryProjectionOutputSchema)
2389
+ memories: z7.array(memoryProjectionOutputSchema)
2032
2390
  });
2033
2391
  function parseMemoryToolInput(schema, input) {
2034
2392
  const result = schema.safeParse(input);
@@ -2219,7 +2577,7 @@ import {
2219
2577
  getSourceKey as getSourceKey2,
2220
2578
  isPrivateSource as isPrivateSource2
2221
2579
  } from "@sentry/junior-plugin-api";
2222
- import { z as z5 } from "zod";
2580
+ import { z as z8 } from "zod";
2223
2581
  var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
2224
2582
  "createMemory",
2225
2583
  "listMemories",
@@ -2227,12 +2585,12 @@ var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
2227
2585
  "searchMemories"
2228
2586
  ]);
2229
2587
  var MEMORY_TASK_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2230
- var extractedMemoryCacheSchema = z5.array(
2231
- z5.object({
2232
- content: z5.string().min(1),
2233
- expiresAtMs: z5.number().finite().nullable(),
2234
- kind: z5.enum(MEMORY_KINDS),
2235
- evidenceMessageIndices: z5.array(z5.number().int().nonnegative()).min(1).max(10)
2588
+ var extractedMemoryCacheSchema = z8.array(
2589
+ z8.object({
2590
+ content: z8.string().min(1),
2591
+ expiresAtMs: z8.number().finite().nullable(),
2592
+ kind: z8.enum(MEMORY_KINDS),
2593
+ evidenceMessageIndices: z8.array(z8.number().int().nonnegative()).min(1).max(10)
2236
2594
  }).strict().transform(parseExtractedMemory)
2237
2595
  );
2238
2596
  function isRunActorInstruction(entry) {
@@ -2371,8 +2729,9 @@ async function processMemorySession(context) {
2371
2729
  import {
2372
2730
  definePromptContext
2373
2731
  } from "@sentry/junior-plugin-api";
2374
- import { z as z6 } from "zod";
2732
+ import { z as z9 } from "zod";
2375
2733
  var DEFAULT_RECALL_LIMIT = 5;
2734
+ var RECALL_CANDIDATE_LIMIT = 20;
2376
2735
  var MAX_PROMPT_CHARS = 4e3;
2377
2736
  var MAX_MEMORY_LINE_CHARS = 600;
2378
2737
  function trimContent(content, maxLength) {
@@ -2385,15 +2744,15 @@ function trimContent(content, maxLength) {
2385
2744
  function formatObservedDate(observedAtMs) {
2386
2745
  return new Date(observedAtMs).toISOString().slice(0, 10);
2387
2746
  }
2388
- var recalledMemorySchema = z6.object({
2389
- id: z6.string().min(1),
2390
- content: z6.string().min(1).max(MAX_MEMORY_LINE_CHARS),
2391
- observedAtMs: z6.number().finite(),
2392
- scope: z6.enum(["personal", "conversation"]),
2393
- kind: z6.enum(["preference", "procedure", "knowledge"])
2747
+ var recalledMemorySchema = z9.object({
2748
+ id: z9.string().min(1),
2749
+ content: z9.string().min(1).max(MAX_MEMORY_LINE_CHARS),
2750
+ observedAtMs: z9.number().finite(),
2751
+ scope: z9.enum(["personal", "conversation"]),
2752
+ kind: z9.enum(["preference", "procedure", "knowledge"])
2394
2753
  }).strict();
2395
- var memoryRecallContextSchema = z6.object({
2396
- memories: z6.array(recalledMemorySchema).min(1).max(DEFAULT_RECALL_LIMIT)
2754
+ var memoryRecallContextSchema = z9.object({
2755
+ memories: z9.array(recalledMemorySchema).min(1).max(DEFAULT_RECALL_LIMIT)
2397
2756
  }).strict();
2398
2757
  function selectPromptMemories(memories) {
2399
2758
  const header = "Relevant memories for this request:";
@@ -2442,14 +2801,31 @@ async function createMemoryPromptContributions(context) {
2442
2801
  ...context.actor ? { actor: context.actor } : {},
2443
2802
  source: context.source
2444
2803
  });
2445
- const memories = await createMemoryStore(context.db, runtimeContext2, {
2804
+ const candidates = await createMemoryStore(context.db, runtimeContext2, {
2446
2805
  ...context.embedder ? { embedder: context.embedder } : {},
2447
2806
  ...context.maxVectorDistance !== void 0 ? { maxVectorDistance: context.maxVectorDistance } : {}
2448
- }).searchMemories({
2807
+ }).recallMemories({
2449
2808
  query: context.text,
2450
- limit: DEFAULT_RECALL_LIMIT
2809
+ limit: RECALL_CANDIDATE_LIMIT
2451
2810
  });
2452
- const selected = selectPromptMemories(memories);
2811
+ if (candidates.length === 0) {
2812
+ return void 0;
2813
+ }
2814
+ let relevantIds;
2815
+ try {
2816
+ relevantIds = await context.agent.selectRelevantMemories({
2817
+ candidates: candidates.map(({ content, id }) => ({ content, id })),
2818
+ userRequest: context.text
2819
+ });
2820
+ } catch {
2821
+ context.log.warn("memory_recall_selection_failed");
2822
+ return void 0;
2823
+ }
2824
+ const candidatesById = new Map(
2825
+ candidates.map((memory) => [memory.id, memory])
2826
+ );
2827
+ const relevant = relevantIds.map((id) => candidatesById.get(id)).filter((memory) => memory !== void 0).slice(0, DEFAULT_RECALL_LIMIT);
2828
+ const selected = selectPromptMemories(relevant);
2453
2829
  if (selected.length === 0) {
2454
2830
  return void 0;
2455
2831
  }
@@ -2460,28 +2836,6 @@ async function createMemoryPromptContributions(context) {
2460
2836
  }
2461
2837
 
2462
2838
  // src/user-pages.ts
2463
- var MEMORY_PAGE_LIMIT = 100;
2464
- function runtimeContext(actor) {
2465
- if (actor.platform === "slack") {
2466
- return {
2467
- actor,
2468
- source: {
2469
- platform: "slack",
2470
- type: "priv",
2471
- teamId: actor.teamId,
2472
- channelId: "DDASHBOARD"
2473
- }
2474
- };
2475
- }
2476
- return {
2477
- actor,
2478
- source: {
2479
- platform: "local",
2480
- type: "priv",
2481
- conversationId: "local:dashboard:memories"
2482
- }
2483
- };
2484
- }
2485
2839
  function memoryKindLabel(kind) {
2486
2840
  return kind.charAt(0).toUpperCase() + kind.slice(1);
2487
2841
  }
@@ -2491,35 +2845,35 @@ function rememberedDate(createdAtMs) {
2491
2845
  timeZone: "UTC"
2492
2846
  }).format(new Date(createdAtMs));
2493
2847
  }
2494
- async function readPersonalMemories(db, actors) {
2495
- const memories = await Promise.all(
2496
- actors.flatMap((actor) => {
2497
- const context = runtimeContext(actor);
2498
- return context ? [
2499
- createMemoryStore(db, context).listPersonalMemories({
2500
- limit: MEMORY_PAGE_LIMIT
2501
- })
2502
- ] : [];
2503
- })
2504
- );
2505
- return [
2506
- ...new Map(memories.flat().map((memory) => [memory.id, memory])).values()
2507
- ].sort((left, right) => right.createdAtMs - left.createdAtMs).slice(0, MEMORY_PAGE_LIMIT);
2508
- }
2509
2848
  function createMemoryUserPage() {
2510
2849
  return {
2511
2850
  id: "memories",
2512
2851
  label: "Memories",
2513
2852
  description: "Personal facts and preferences Junior remembers about you.",
2514
- async read(ctx) {
2515
- const memories = await readPersonalMemories(
2853
+ async read(ctx, input) {
2854
+ const page = await createViewerMemories(
2516
2855
  ctx.db,
2517
2856
  ctx.viewer.actors
2518
- );
2857
+ ).list({
2858
+ cursor: input.cursor,
2859
+ limit: input.limit,
2860
+ ...input.query ? { query: input.query } : {}
2861
+ });
2519
2862
  return {
2520
2863
  type: "list",
2521
- emptyText: "No personal memories yet.",
2522
- records: memories.map((memory) => ({
2864
+ emptyText: input.query ? "No memories matched your search." : "No personal memories yet.",
2865
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
2866
+ searchPlaceholder: "Search memories",
2867
+ records: page.memories.map((memory) => ({
2868
+ actions: [
2869
+ {
2870
+ confirmation: "Forget this memory?",
2871
+ href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`,
2872
+ label: "Forget",
2873
+ method: "DELETE",
2874
+ tone: "danger"
2875
+ }
2876
+ ],
2523
2877
  id: memory.id,
2524
2878
  title: memory.content,
2525
2879
  metadata: [
@@ -2574,7 +2928,7 @@ function memoryCreateToolContext(ctx) {
2574
2928
  supersessionDecider: ctx.supersessionDecider
2575
2929
  };
2576
2930
  }
2577
- function createMemoryPlugin(options = {}) {
2931
+ function memoryPlugin(options = {}) {
2578
2932
  const modelId = memoryModelId(options);
2579
2933
  return defineJuniorPlugin({
2580
2934
  manifest: {
@@ -2596,6 +2950,12 @@ function createMemoryPlugin(options = {}) {
2596
2950
  },
2597
2951
  userPages: [createMemoryUserPage()],
2598
2952
  hooks: {
2953
+ apiRoutes(ctx) {
2954
+ return createMemoryApi({
2955
+ actors: ctx.viewer.actors,
2956
+ db: ctx.db
2957
+ });
2958
+ },
2599
2959
  tools(ctx) {
2600
2960
  const agent = createMemoryAgent(ctx.model);
2601
2961
  const context = memoryToolContext({
@@ -2621,10 +2981,12 @@ function createMemoryPlugin(options = {}) {
2621
2981
  },
2622
2982
  async userPrompt(ctx) {
2623
2983
  return await createMemoryPromptContributions({
2984
+ agent: createMemoryAgent(ctx.model),
2624
2985
  ...ctx.conversationId ? { conversationId: ctx.conversationId } : {},
2625
2986
  ...ctx.actor ? { actor: ctx.actor } : {},
2626
2987
  db: ctx.db,
2627
2988
  embedder: ctx.embedder,
2989
+ log: ctx.log,
2628
2990
  maxVectorDistance: recallMaxVectorDistance(options),
2629
2991
  source: ctx.source,
2630
2992
  text: ctx.text
@@ -2633,11 +2995,11 @@ function createMemoryPlugin(options = {}) {
2633
2995
  }
2634
2996
  });
2635
2997
  }
2636
- var memoryPlugin = createMemoryPlugin();
2637
2998
  export {
2638
2999
  MEMORY_KINDS,
2639
- createMemoryPlugin,
2640
3000
  createMemoryStore,
3001
+ memoryApiSchema,
3002
+ memoryListResponseSchema,
2641
3003
  memoryPlugin
2642
3004
  };
2643
3005
  //# sourceMappingURL=index.js.map