@sentry/junior-memory 0.180.0 → 0.181.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 +25 -24
- package/dist/agent.d.ts +4 -0
- package/dist/api.d.ts +3 -4
- package/dist/db/schema.d.ts +19 -2
- package/dist/events.d.ts +3 -3
- package/dist/index.js +487 -666
- package/dist/index.js.map +1 -1
- package/dist/process-session.d.ts +3 -4
- package/dist/ranking.d.ts +0 -2
- package/dist/recall.d.ts +9 -2
- package/dist/scope.d.ts +11 -15
- package/dist/store.d.ts +6 -7
- package/dist/tools.d.ts +8 -1
- package/dist/types.d.ts +4 -2
- package/dist/user-pages.d.ts +1 -1
- package/dist/viewer.d.ts +85 -0
- package/migrations/0009_faithful_whizzer.sql +114 -0
- package/migrations/meta/0009_snapshot.json +411 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +2 -2
- package/src/agent.ts +6 -8
- package/src/api.ts +33 -24
- package/src/db/schema.ts +3 -1
- package/src/events.ts +30 -8
- package/src/operational-report.ts +20 -20
- package/src/plugin.ts +13 -1
- package/src/process-session.ts +21 -35
- package/src/ranking.ts +9 -26
- package/src/recall.ts +11 -1
- package/src/scope.ts +34 -135
- package/src/store.ts +34 -91
- package/src/tools.ts +30 -14
- package/src/types.ts +5 -2
- package/src/user-pages.ts +4 -6
- package/src/viewer.ts +393 -0
- package/dist/personal-store.d.ts +0 -92
- package/dist/personal.d.ts +0 -31
- package/src/personal-store.ts +0 -421
- package/src/personal.ts +0 -140
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "drizzle-orm";
|
|
26
26
|
import { cosineDistance } from "drizzle-orm/sql/functions";
|
|
27
27
|
import { z as z2 } from "zod";
|
|
28
|
+
import { getSourceKey } from "@sentry/junior-plugin-api";
|
|
28
29
|
|
|
29
30
|
// src/db/schema.ts
|
|
30
31
|
import { sql } from "drizzle-orm";
|
|
@@ -44,7 +45,7 @@ import {
|
|
|
44
45
|
import { actorSchema, sourceSchema } from "@sentry/junior-plugin-api";
|
|
45
46
|
import { z } from "zod";
|
|
46
47
|
var MEMORY_KINDS = ["preference", "procedure", "knowledge"];
|
|
47
|
-
var MEMORY_SCOPES = ["
|
|
48
|
+
var MEMORY_SCOPES = ["private", "public"];
|
|
48
49
|
var MEMORY_SUBJECT_TYPES = [
|
|
49
50
|
"user",
|
|
50
51
|
"conversation",
|
|
@@ -56,8 +57,11 @@ var MEMORY_EMBEDDING_DIMENSIONS = 1536;
|
|
|
56
57
|
var nonEmptyStringSchema = z.string().min(1);
|
|
57
58
|
var memoryRuntimeContextSchema = z.object({
|
|
58
59
|
conversationId: nonEmptyStringSchema.optional(),
|
|
60
|
+
locationId: nonEmptyStringSchema.optional(),
|
|
59
61
|
actor: actorSchema.optional(),
|
|
60
|
-
source: sourceSchema
|
|
62
|
+
source: sourceSchema,
|
|
63
|
+
/** User linked to the active Actor. */
|
|
64
|
+
userId: nonEmptyStringSchema.optional()
|
|
61
65
|
}).strict();
|
|
62
66
|
|
|
63
67
|
// src/db/schema.ts
|
|
@@ -83,6 +87,8 @@ var juniorMemoryMemories = pgTable(
|
|
|
83
87
|
enum: MEMORY_SOURCE_PLATFORMS
|
|
84
88
|
}).notNull(),
|
|
85
89
|
sourceKey: text("source_key").notNull(),
|
|
90
|
+
/** Location where Junior learned the memory, when known. */
|
|
91
|
+
locationId: text("location_id"),
|
|
86
92
|
idempotencyKey: text("idempotency_key"),
|
|
87
93
|
observedAtMs: bigint("observed_at_ms", { mode: "number" }).notNull(),
|
|
88
94
|
createdAtMs: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
@@ -107,7 +113,7 @@ var juniorMemoryMemories = pgTable(
|
|
|
107
113
|
),
|
|
108
114
|
check(
|
|
109
115
|
"junior_memory_memories_scope_check",
|
|
110
|
-
sql`${table.scope} IN ('
|
|
116
|
+
sql`${table.scope} IN ('private', 'public')`
|
|
111
117
|
),
|
|
112
118
|
check(
|
|
113
119
|
"junior_memory_memories_kind_check",
|
|
@@ -176,9 +182,6 @@ function reciprocalRank(rank, weight) {
|
|
|
176
182
|
function matchScore(match, weights) {
|
|
177
183
|
return (match.vector ? reciprocalRank(match.vector.rank, weights.vectorWeight) : 0) + (match.lexical ? reciprocalRank(match.lexical.rank, weights.lexicalWeight) : 0);
|
|
178
184
|
}
|
|
179
|
-
function currentChannel(match, channelPrefix) {
|
|
180
|
-
return channelPrefix ? match.sourceKey.startsWith(channelPrefix) : false;
|
|
181
|
-
}
|
|
182
185
|
function observedAgeRank(memory, nowMs) {
|
|
183
186
|
const ageMs = Math.max(0, nowMs - memory.observedAtMs);
|
|
184
187
|
if (ageMs <= 7 * ONE_DAY_MS) {
|
|
@@ -218,140 +221,55 @@ function rankMemoryMatches(matches, options) {
|
|
|
218
221
|
if (scoreDelta !== 0) {
|
|
219
222
|
return scoreDelta;
|
|
220
223
|
}
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
const channelDelta = Number(currentChannel(right, options.channelPrefix)) - Number(currentChannel(left, options.channelPrefix));
|
|
226
|
-
if (channelDelta !== 0) {
|
|
227
|
-
return channelDelta;
|
|
224
|
+
const privateDelta = Number(right.memory.scope === "private") - Number(left.memory.scope === "private");
|
|
225
|
+
if (privateDelta !== 0) {
|
|
226
|
+
return privateDelta;
|
|
228
227
|
}
|
|
229
228
|
return observedAgeRank(right.memory, options.nowMs) - observedAgeRank(left.memory, options.nowMs) || right.memory.observedAtMs - left.memory.observedAtMs || left.memory.id.localeCompare(right.memory.id);
|
|
230
229
|
});
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
// src/scope.ts
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
function personalScopeFromIdentity(identity) {
|
|
242
|
-
if (identity.provider === "local") {
|
|
243
|
-
return {
|
|
244
|
-
scope: "personal",
|
|
245
|
-
scopeKey: `local:${identity.providerSubjectId}`
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
if (identity.provider === "junior") {
|
|
249
|
-
return {
|
|
250
|
-
scope: "personal",
|
|
251
|
-
scopeKey: `junior:${identity.providerSubjectId}`
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (identity.provider === "slack" && identity.providerTenantId) {
|
|
255
|
-
return {
|
|
256
|
-
scope: "personal",
|
|
257
|
-
scopeKey: `slack:${identity.providerTenantId}:${identity.providerSubjectId}`
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
return void 0;
|
|
261
|
-
}
|
|
262
|
-
function deriveViewerMemoryScopes(identities) {
|
|
263
|
-
const privateScopes = identities.flatMap((identity) => {
|
|
264
|
-
const scope = personalScopeFromIdentity(identity);
|
|
265
|
-
return scope ? [scope] : [];
|
|
266
|
-
});
|
|
267
|
-
const publicScopes = identities.flatMap(
|
|
268
|
-
(identity) => identity.provider === "slack" && identity.providerTenantId ? [
|
|
269
|
-
{
|
|
270
|
-
scope: "conversation",
|
|
271
|
-
scopeKey: `slack:${identity.providerTenantId}`
|
|
272
|
-
}
|
|
273
|
-
] : []
|
|
274
|
-
);
|
|
275
|
-
return {
|
|
276
|
-
privateScopes: uniqueScopes(privateScopes),
|
|
277
|
-
publicScopes: uniqueScopes(publicScopes)
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
function sourceConversationKey(source) {
|
|
281
|
-
switch (source.platform) {
|
|
282
|
-
case "web":
|
|
283
|
-
case "local":
|
|
284
|
-
return source.conversationId;
|
|
285
|
-
case "slack": {
|
|
286
|
-
if (source.visibility === "public") {
|
|
287
|
-
return `slack:${source.teamId}`;
|
|
288
|
-
}
|
|
289
|
-
const threadKey = source.threadTs ?? source.messageTs;
|
|
290
|
-
if (!threadKey) {
|
|
291
|
-
return void 0;
|
|
292
|
-
}
|
|
293
|
-
return `slack:${source.teamId}:${source.channelId}:${threadKey}`;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function actorScopeKey(actor) {
|
|
298
|
-
if (!actor) {
|
|
299
|
-
return void 0;
|
|
300
|
-
}
|
|
301
|
-
switch (actor.platform) {
|
|
302
|
-
case "system":
|
|
303
|
-
return void 0;
|
|
304
|
-
case "slack":
|
|
305
|
-
return `slack:${actor.teamId}:${actor.userId}`;
|
|
306
|
-
case "local":
|
|
307
|
-
return `local:${actor.userId}`;
|
|
308
|
-
case "web": {
|
|
309
|
-
const email = actor.email?.trim().toLowerCase();
|
|
310
|
-
return email ? `junior:${email}` : void 0;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
233
|
+
var PUBLIC_SCOPE_KEY = "public";
|
|
234
|
+
var publicMemoryScope = {
|
|
235
|
+
scope: "public",
|
|
236
|
+
scopeKey: PUBLIC_SCOPE_KEY
|
|
237
|
+
};
|
|
238
|
+
function privateMemoryScope(userId) {
|
|
239
|
+
return { scope: "private", scopeKey: userId };
|
|
313
240
|
}
|
|
314
|
-
function deriveMemoryScope(ctx
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
if (!scopeKey2) {
|
|
318
|
-
throw new Error("Personal memory requires actor context.");
|
|
319
|
-
}
|
|
320
|
-
return { scope, scopeKey: scopeKey2 };
|
|
241
|
+
function deriveMemoryScope(ctx) {
|
|
242
|
+
if (ctx.source.visibility === "public") {
|
|
243
|
+
return publicMemoryScope;
|
|
321
244
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
throw new Error("Conversation memory requires conversation context.");
|
|
245
|
+
if (!ctx.userId) {
|
|
246
|
+
throw new Error("Private memory requires a User.");
|
|
325
247
|
}
|
|
326
|
-
return
|
|
248
|
+
return privateMemoryScope(ctx.userId);
|
|
327
249
|
}
|
|
328
|
-
function deriveMemorySubject(ctx,
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
throw new Error("User-subject memory requires actor context.");
|
|
250
|
+
function deriveMemorySubject(ctx, subjectType) {
|
|
251
|
+
if (subjectType === "user") {
|
|
252
|
+
if (!ctx.userId) {
|
|
253
|
+
throw new Error("User memory requires a User.");
|
|
333
254
|
}
|
|
334
|
-
return { subjectType
|
|
255
|
+
return { subjectType, subjectKey: ctx.userId };
|
|
335
256
|
}
|
|
336
|
-
const subjectKey =
|
|
257
|
+
const subjectKey = ctx.conversationId;
|
|
337
258
|
if (!subjectKey) {
|
|
338
259
|
throw new Error(
|
|
339
260
|
"Conversation-subject memory requires conversation context."
|
|
340
261
|
);
|
|
341
262
|
}
|
|
342
|
-
return {
|
|
263
|
+
return {
|
|
264
|
+
subjectType,
|
|
265
|
+
subjectKey
|
|
266
|
+
};
|
|
343
267
|
}
|
|
344
268
|
function deriveVisibleMemoryScopes(ctx) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
scopes.push(deriveMemoryScope(ctx, "personal"));
|
|
348
|
-
} catch {
|
|
349
|
-
}
|
|
350
|
-
try {
|
|
351
|
-
scopes.push(deriveMemoryScope(ctx, "conversation"));
|
|
352
|
-
} catch {
|
|
269
|
+
if (!ctx.userId) {
|
|
270
|
+
return [publicMemoryScope];
|
|
353
271
|
}
|
|
354
|
-
return
|
|
272
|
+
return [publicMemoryScope, privateMemoryScope(ctx.userId)];
|
|
355
273
|
}
|
|
356
274
|
|
|
357
275
|
// src/store.ts
|
|
@@ -418,6 +336,7 @@ var memoryRowSchema = z2.object({
|
|
|
418
336
|
expiresAtMs: optionalNumberSchema,
|
|
419
337
|
id: z2.string().min(1),
|
|
420
338
|
idempotencyKey: optionalStringSchema,
|
|
339
|
+
locationId: optionalNonEmptyStringSchema,
|
|
421
340
|
observedAtMs: z2.coerce.number(),
|
|
422
341
|
searchVector: z2.string().optional(),
|
|
423
342
|
scope: z2.enum(MEMORY_SCOPES),
|
|
@@ -515,40 +434,12 @@ function boundedLimit(value, fallback) {
|
|
|
515
434
|
}
|
|
516
435
|
return Math.min(200, Math.max(1, Math.floor(value)));
|
|
517
436
|
}
|
|
518
|
-
function memorySourcePlatform(source) {
|
|
519
|
-
switch (source.platform) {
|
|
520
|
-
case "slack":
|
|
521
|
-
return "slack";
|
|
522
|
-
case "local":
|
|
523
|
-
return "local";
|
|
524
|
-
case "web":
|
|
525
|
-
return "web";
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
437
|
function sourceKey(ctx) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return ctx.source.conversationId;
|
|
533
|
-
case "slack": {
|
|
534
|
-
const threadKey = ctx.source.threadTs ?? ctx.source.messageTs;
|
|
535
|
-
if (!threadKey) {
|
|
536
|
-
throw new Error(
|
|
537
|
-
"Memory source requires a Slack message or thread timestamp."
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:${threadKey}`;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
function sourceChannelPrefix(ctx) {
|
|
545
|
-
switch (ctx.source.platform) {
|
|
546
|
-
case "slack":
|
|
547
|
-
return `slack:${ctx.source.teamId}:${ctx.source.channelId}:`;
|
|
548
|
-
case "web":
|
|
549
|
-
case "local":
|
|
550
|
-
return void 0;
|
|
438
|
+
const key = getSourceKey(ctx.source);
|
|
439
|
+
if (!key) {
|
|
440
|
+
throw new Error("Memory Source has no stable key.");
|
|
551
441
|
}
|
|
442
|
+
return key;
|
|
552
443
|
}
|
|
553
444
|
function parseMemoryRow(row) {
|
|
554
445
|
const parsed = memoryRowSchema.parse(row);
|
|
@@ -581,12 +472,12 @@ function visibleScopePredicate(scopes) {
|
|
|
581
472
|
);
|
|
582
473
|
}
|
|
583
474
|
function activeVisiblePredicate(args) {
|
|
584
|
-
const
|
|
585
|
-
if (!
|
|
475
|
+
const scopePredicate = visibleScopePredicate(args.scopes);
|
|
476
|
+
if (!scopePredicate) {
|
|
586
477
|
return void 0;
|
|
587
478
|
}
|
|
588
479
|
return and(
|
|
589
|
-
|
|
480
|
+
scopePredicate,
|
|
590
481
|
isNull(juniorMemoryMemories.archivedAtMs),
|
|
591
482
|
isNull(juniorMemoryMemories.supersededAtMs),
|
|
592
483
|
isNull(juniorMemoryMemories.supersededById),
|
|
@@ -656,12 +547,12 @@ async function findByIdempotencyKey(args) {
|
|
|
656
547
|
return void 0;
|
|
657
548
|
}
|
|
658
549
|
async function archiveExpiredMemoryBatch(args) {
|
|
659
|
-
const
|
|
660
|
-
if (!
|
|
550
|
+
const scopePredicate = visibleScopePredicate(args.scopes);
|
|
551
|
+
if (!scopePredicate) {
|
|
661
552
|
return { archivedCount: 0 };
|
|
662
553
|
}
|
|
663
554
|
const predicates = [
|
|
664
|
-
|
|
555
|
+
scopePredicate,
|
|
665
556
|
isNull(juniorMemoryMemories.archivedAtMs),
|
|
666
557
|
isNull(juniorMemoryMemories.supersededAtMs),
|
|
667
558
|
isNull(juniorMemoryMemories.supersededById),
|
|
@@ -769,7 +660,7 @@ function activeScopedSubjectPredicate(args) {
|
|
|
769
660
|
eq(juniorMemoryMemories.scopeKey, args.scope.scopeKey),
|
|
770
661
|
eq(juniorMemoryMemories.kind, args.kind),
|
|
771
662
|
eq(juniorMemoryMemories.subjectType, args.subject.subjectType),
|
|
772
|
-
|
|
663
|
+
eq(juniorMemoryMemories.subjectKey, args.subject.subjectKey),
|
|
773
664
|
isNull(juniorMemoryMemories.archivedAtMs),
|
|
774
665
|
isNull(juniorMemoryMemories.supersededAtMs),
|
|
775
666
|
isNull(juniorMemoryMemories.supersededById),
|
|
@@ -809,11 +700,12 @@ async function rememberDuplicateIdempotency(args) {
|
|
|
809
700
|
targetId: args.duplicate.id
|
|
810
701
|
}),
|
|
811
702
|
idempotencyKey: args.idempotencyKey,
|
|
703
|
+
locationId: args.runtimeContext.locationId,
|
|
812
704
|
observedAtMs: args.nowMs,
|
|
813
705
|
scope: args.scope.scope,
|
|
814
706
|
scopeKey: args.scope.scopeKey,
|
|
815
707
|
sourceKey: sourceKey(args.runtimeContext),
|
|
816
|
-
sourcePlatform:
|
|
708
|
+
sourcePlatform: args.runtimeContext.source.platform,
|
|
817
709
|
subjectKey: args.subject.subjectKey,
|
|
818
710
|
subjectType: args.subject.subjectType,
|
|
819
711
|
supersededAtMs: args.nowMs,
|
|
@@ -998,8 +890,7 @@ async function searchVisibleLexicalMemories(args) {
|
|
|
998
890
|
const ranks = denseRanks(rows, (row) => Number(row.textRank));
|
|
999
891
|
return rows.map((row, index2) => ({
|
|
1000
892
|
lexical: { rank: ranks[index2] },
|
|
1001
|
-
memory: parseMemoryRow(row.memory)
|
|
1002
|
-
sourceKey: row.memory.sourceKey
|
|
893
|
+
memory: parseMemoryRow(row.memory)
|
|
1003
894
|
}));
|
|
1004
895
|
}
|
|
1005
896
|
async function searchVisibleVectorMemories(args) {
|
|
@@ -1043,7 +934,6 @@ async function searchVisibleVectorMemories(args) {
|
|
|
1043
934
|
return [
|
|
1044
935
|
{
|
|
1045
936
|
memory: parseMemoryRow(row.memory),
|
|
1046
|
-
sourceKey: row.memory.sourceKey,
|
|
1047
937
|
vector: {
|
|
1048
938
|
rank: ranks[index2]
|
|
1049
939
|
}
|
|
@@ -1081,12 +971,12 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1081
971
|
});
|
|
1082
972
|
return { created: false, memory: args.duplicate };
|
|
1083
973
|
}
|
|
1084
|
-
async function createScopedMemory(rawInput,
|
|
974
|
+
async function createScopedMemory(rawInput, subjectType) {
|
|
1085
975
|
const input = createMemoryInputSchema.parse(rawInput);
|
|
1086
976
|
const nowMs = getNowMs();
|
|
1087
977
|
const content = normalizeContent(input.content);
|
|
1088
|
-
const scope = deriveMemoryScope(runtimeContext
|
|
1089
|
-
const subject = deriveMemorySubject(runtimeContext,
|
|
978
|
+
const scope = deriveMemoryScope(runtimeContext);
|
|
979
|
+
const subject = deriveMemorySubject(runtimeContext, subjectType);
|
|
1090
980
|
if (content.length > MAX_MEMORY_CONTENT_CHARS) {
|
|
1091
981
|
throw new Error("Memory content exceeds the maximum length.");
|
|
1092
982
|
}
|
|
@@ -1147,7 +1037,7 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1147
1037
|
}
|
|
1148
1038
|
}
|
|
1149
1039
|
let supersededIds = [];
|
|
1150
|
-
if (
|
|
1040
|
+
if (subjectType === "user" && input.kind === "preference" && supersessionDecider && (input.expiresAtMs === void 0 || input.expiresAtMs > nowMs)) {
|
|
1151
1041
|
const preferenceCandidates = await listPreferenceAdjudicationCandidates({
|
|
1152
1042
|
db,
|
|
1153
1043
|
...candidateEmbedding ? { embedding: candidateEmbedding } : void 0,
|
|
@@ -1183,11 +1073,12 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1183
1073
|
expiresAtMs: input.expiresAtMs,
|
|
1184
1074
|
id,
|
|
1185
1075
|
idempotencyKey: input.idempotencyKey,
|
|
1076
|
+
locationId: runtimeContext.locationId,
|
|
1186
1077
|
observedAtMs: nowMs,
|
|
1187
1078
|
scope: scope.scope,
|
|
1188
1079
|
scopeKey: scope.scopeKey,
|
|
1189
1080
|
sourceKey: sourceKey(runtimeContext),
|
|
1190
|
-
sourcePlatform:
|
|
1081
|
+
sourcePlatform: runtimeContext.source.platform,
|
|
1191
1082
|
subjectKey: subject.subjectKey,
|
|
1192
1083
|
subjectType: subject.subjectType,
|
|
1193
1084
|
kind: input.kind
|
|
@@ -1269,8 +1160,8 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1269
1160
|
const limit = boundedLimit(input.limit, DEFAULT_SEARCH_LIMIT);
|
|
1270
1161
|
const overfetch = vectorMaxDistance === void 0 ? SEARCH_RETRIEVAL_OVERFETCH : RECALL_RETRIEVAL_OVERFETCH;
|
|
1271
1162
|
const candidateLimit = retrievalLegLimit(limit, overfetch);
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1163
|
+
const privateScopes = scopes.filter((scope) => scope.scope === "private");
|
|
1164
|
+
const probePrivate = vectorMaxDistance !== void 0 && privateScopes.length > 0;
|
|
1274
1165
|
const query = normalizeRetrievalQuery(input.query);
|
|
1275
1166
|
let queryEmbedding;
|
|
1276
1167
|
if (embedder && query) {
|
|
@@ -1300,25 +1191,23 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1300
1191
|
...lexicalArgs,
|
|
1301
1192
|
scopes
|
|
1302
1193
|
}),
|
|
1303
|
-
queryEmbedding &&
|
|
1194
|
+
queryEmbedding && probePrivate ? searchVisibleVectorMemories({
|
|
1304
1195
|
db,
|
|
1305
1196
|
embedding: queryEmbedding,
|
|
1306
1197
|
limit: candidateLimit,
|
|
1307
1198
|
maxDistance: vectorMaxDistance,
|
|
1308
1199
|
nowMs,
|
|
1309
|
-
scopes:
|
|
1200
|
+
scopes: privateScopes
|
|
1310
1201
|
}) : emptyMatches,
|
|
1311
|
-
|
|
1202
|
+
probePrivate ? searchVisibleLexicalMemories({
|
|
1312
1203
|
...lexicalArgs,
|
|
1313
|
-
scopes:
|
|
1204
|
+
scopes: privateScopes
|
|
1314
1205
|
}) : emptyMatches
|
|
1315
1206
|
]);
|
|
1316
|
-
const channelPrefix = sourceChannelPrefix(runtimeContext);
|
|
1317
1207
|
return rankMemoryMatches(matches.flat(), {
|
|
1318
1208
|
nowMs,
|
|
1319
1209
|
// Slight lexical preference protects exact ids/names/timezones on ties.
|
|
1320
|
-
...vectorMaxDistance === void 0 ? void 0 : { lexicalWeight: 1, vectorWeight: 0.85 }
|
|
1321
|
-
...channelPrefix ? { channelPrefix } : void 0
|
|
1210
|
+
...vectorMaxDistance === void 0 ? void 0 : { lexicalWeight: 1, vectorWeight: 0.85 }
|
|
1322
1211
|
}).slice(0, limit).map(({ memory }) => memory);
|
|
1323
1212
|
}
|
|
1324
1213
|
return {
|
|
@@ -1326,7 +1215,7 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1326
1215
|
return await archiveExpiredVisibleMemories(input, getNowMs());
|
|
1327
1216
|
},
|
|
1328
1217
|
async createMemory(input) {
|
|
1329
|
-
return await createScopedMemory(input, "
|
|
1218
|
+
return await createScopedMemory(input, "user");
|
|
1330
1219
|
},
|
|
1331
1220
|
async createConversationMemory(input) {
|
|
1332
1221
|
return await createScopedMemory(input, "conversation");
|
|
@@ -1347,22 +1236,6 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1347
1236
|
scopes
|
|
1348
1237
|
});
|
|
1349
1238
|
},
|
|
1350
|
-
async listPersonalMemories(input) {
|
|
1351
|
-
input = listMemoriesInputSchema.parse(input);
|
|
1352
|
-
const nowMs = getNowMs();
|
|
1353
|
-
const scopes = [deriveMemoryScope(runtimeContext, "personal")];
|
|
1354
|
-
await archiveExpiredMemoryBatch({
|
|
1355
|
-
db,
|
|
1356
|
-
nowMs,
|
|
1357
|
-
scopes
|
|
1358
|
-
});
|
|
1359
|
-
return await listVisibleMemories({
|
|
1360
|
-
db,
|
|
1361
|
-
limit: input.limit,
|
|
1362
|
-
nowMs,
|
|
1363
|
-
scopes
|
|
1364
|
-
});
|
|
1365
|
-
},
|
|
1366
1239
|
async recallMemories(input) {
|
|
1367
1240
|
return await retrieveVisibleMemories(input, RECALL_MAX_VECTOR_DISTANCE);
|
|
1368
1241
|
},
|
|
@@ -1372,7 +1245,9 @@ function createMemoryStore(db, context, options = {}) {
|
|
|
1372
1245
|
async archiveMemory(input) {
|
|
1373
1246
|
input = archiveMemoryInputSchema.parse(input);
|
|
1374
1247
|
const nowMs = getNowMs();
|
|
1375
|
-
const scopes = deriveVisibleMemoryScopes(runtimeContext)
|
|
1248
|
+
const scopes = deriveVisibleMemoryScopes(runtimeContext).filter(
|
|
1249
|
+
(scope) => scope.scope === "private"
|
|
1250
|
+
);
|
|
1376
1251
|
const predicate = activeVisiblePredicate({ nowMs, scopes });
|
|
1377
1252
|
const idPrefix = input.id.trim();
|
|
1378
1253
|
if (!idPrefix) {
|
|
@@ -1517,13 +1392,13 @@ var extractedMemoryResultSchema = z3.object({
|
|
|
1517
1392
|
}).strict();
|
|
1518
1393
|
var extractMemoriesResponseSchema = z3.object({
|
|
1519
1394
|
memories: z3.array(extractedMemorySchema).max(5).describe(
|
|
1520
|
-
"Accepted
|
|
1395
|
+
"Accepted durable memories from the completed run. Return one object per distinct source assertion and classify it with kind."
|
|
1521
1396
|
)
|
|
1522
1397
|
}).strict();
|
|
1523
1398
|
var MEMORY_REVIEW_SYSTEM = [
|
|
1524
1399
|
"You are Junior's memory review agent.",
|
|
1525
1400
|
"Review one memory candidate and return one structured review decision.",
|
|
1526
|
-
"Store only
|
|
1401
|
+
"Store only self-contained facts that are useful beyond this turn and safe for the current Source.",
|
|
1527
1402
|
"Reject secrets, credentials, private or sensitive personal details, gossip, speculative claims about other people, assistant/system implementation details, vague references, and low-durability chatter.",
|
|
1528
1403
|
"Use the runtime context only for authority and scope; do not accept model-provided actor ids, scope ids, aliases, or arbitrary subjects."
|
|
1529
1404
|
].join("\n");
|
|
@@ -1532,7 +1407,7 @@ var MEMORY_EXTRACTION_SYSTEM = [
|
|
|
1532
1407
|
"Use the completed run transcript as source evidence, including user-authored messages and tool results.",
|
|
1533
1408
|
"Assistant text is context for interpreting the run, not independent evidence for new facts.",
|
|
1534
1409
|
"Reject secrets, credentials, private or sensitive personal details, gossip, speculative claims about other people, assistant/system implementation details, vague references, and low-durability chatter.",
|
|
1535
|
-
"If no
|
|
1410
|
+
"If no durable, self-contained memory remains after rewriting, return an empty memories array."
|
|
1536
1411
|
].join("\n");
|
|
1537
1412
|
var MEMORY_RECALL_SYSTEM = [
|
|
1538
1413
|
"You are Junior's memory recall relevance agent.",
|
|
@@ -1650,7 +1525,7 @@ function reviewPrompt(request) {
|
|
|
1650
1525
|
"</candidate>",
|
|
1651
1526
|
"",
|
|
1652
1527
|
"<rules>",
|
|
1653
|
-
"- Return store only when the candidate is
|
|
1528
|
+
"- Return store only when the candidate is durable, self-contained, and safe for the current Source.",
|
|
1654
1529
|
"- First classify the memory kind: preference, procedure, or knowledge.",
|
|
1655
1530
|
"- Use kind=preference only for first-person facts authored by the current actor about their own preference, opinion, habit, identity, or workflow.",
|
|
1656
1531
|
"- Reject named third-person personal facts such as another person's preference, opinion, habit, identity, relationship, or workflow. Do not assume a named person is the current actor.",
|
|
@@ -1889,49 +1764,85 @@ function parseCreateMemoryRequest(request) {
|
|
|
1889
1764
|
}
|
|
1890
1765
|
|
|
1891
1766
|
// src/api.ts
|
|
1892
|
-
import { z as
|
|
1767
|
+
import { z as z5 } from "zod";
|
|
1893
1768
|
import {
|
|
1894
1769
|
pluginApiRouteRequestContextSchema
|
|
1895
1770
|
} from "@sentry/junior-plugin-api";
|
|
1896
1771
|
|
|
1897
|
-
// src/
|
|
1898
|
-
import {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1772
|
+
// src/viewer.ts
|
|
1773
|
+
import {
|
|
1774
|
+
and as and2,
|
|
1775
|
+
asc as asc2,
|
|
1776
|
+
desc as desc2,
|
|
1777
|
+
eq as eq2,
|
|
1778
|
+
gt as gt2,
|
|
1779
|
+
ilike,
|
|
1780
|
+
isNull as isNull2,
|
|
1781
|
+
like as like2,
|
|
1782
|
+
lt,
|
|
1783
|
+
or as or2,
|
|
1784
|
+
sql as sql3
|
|
1785
|
+
} from "drizzle-orm";
|
|
1902
1786
|
import { z as z4 } from "zod";
|
|
1787
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1903
1788
|
var nonEmptyStringSchema3 = z4.string().min(1);
|
|
1904
1789
|
var memoryVisibilitySchema = z4.enum(["private", "public"]);
|
|
1905
|
-
var
|
|
1790
|
+
var cursorSchema = z4.object({
|
|
1906
1791
|
createdAtMs: z4.number().finite(),
|
|
1907
|
-
id: nonEmptyStringSchema3
|
|
1792
|
+
id: nonEmptyStringSchema3,
|
|
1793
|
+
kind: z4.enum(MEMORY_KINDS).optional(),
|
|
1794
|
+
origin: z4.enum(["automatic", "explicit"]).optional(),
|
|
1795
|
+
query: z4.string().max(200).optional(),
|
|
1796
|
+
version: z4.literal(1),
|
|
1797
|
+
visibility: memoryVisibilitySchema.optional()
|
|
1908
1798
|
}).strict();
|
|
1909
|
-
var
|
|
1910
|
-
cursor:
|
|
1799
|
+
var pageInputSchema = z4.object({
|
|
1800
|
+
cursor: z4.string().min(1).max(1e3).optional(),
|
|
1911
1801
|
kind: z4.enum(MEMORY_KINDS).optional(),
|
|
1912
1802
|
limit: z4.number().int().min(1).max(50),
|
|
1913
1803
|
origin: z4.enum(["automatic", "explicit"]).optional(),
|
|
1914
1804
|
query: z4.string().max(200).optional(),
|
|
1915
1805
|
visibility: memoryVisibilitySchema.optional()
|
|
1916
1806
|
}).strict();
|
|
1917
|
-
var
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1807
|
+
var timelineDaysSchema = z4.number().int().min(1).max(365);
|
|
1808
|
+
var InvalidMemoryCursorError = class extends Error {
|
|
1809
|
+
constructor() {
|
|
1810
|
+
super("Memory cursor is invalid.");
|
|
1811
|
+
this.name = "InvalidMemoryCursorError";
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
var MemoryNotFoundError = class extends Error {
|
|
1922
1815
|
constructor() {
|
|
1923
|
-
super("Memory was not found for
|
|
1924
|
-
this.name = "
|
|
1816
|
+
super("Memory was not found for this user.");
|
|
1817
|
+
this.name = "MemoryNotFoundError";
|
|
1925
1818
|
}
|
|
1926
1819
|
};
|
|
1927
|
-
function
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1820
|
+
function publicScopePredicate() {
|
|
1821
|
+
return and2(
|
|
1822
|
+
eq2(juniorMemoryMemories.scope, publicMemoryScope.scope),
|
|
1823
|
+
eq2(juniorMemoryMemories.scopeKey, publicMemoryScope.scopeKey)
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
function privateScopePredicate(userId) {
|
|
1827
|
+
return and2(
|
|
1828
|
+
eq2(juniorMemoryMemories.scope, "private"),
|
|
1829
|
+
eq2(juniorMemoryMemories.scopeKey, userId)
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
function visibleScopePredicate2(userId, visibility) {
|
|
1833
|
+
if (visibility === "public") return publicScopePredicate();
|
|
1834
|
+
if (visibility === "private") return privateScopePredicate(userId);
|
|
1835
|
+
return or2(publicScopePredicate(), privateScopePredicate(userId));
|
|
1836
|
+
}
|
|
1837
|
+
function activeMemoryPredicate(userId, nowMs, visibility) {
|
|
1838
|
+
return and2(
|
|
1839
|
+
visibleScopePredicate2(userId, visibility),
|
|
1840
|
+
isNull2(juniorMemoryMemories.archivedAtMs),
|
|
1841
|
+
isNull2(juniorMemoryMemories.supersededAtMs),
|
|
1842
|
+
isNull2(juniorMemoryMemories.supersededById),
|
|
1843
|
+
or2(
|
|
1844
|
+
isNull2(juniorMemoryMemories.expiresAtMs),
|
|
1845
|
+
gt2(juniorMemoryMemories.expiresAtMs, nowMs)
|
|
1935
1846
|
)
|
|
1936
1847
|
);
|
|
1937
1848
|
}
|
|
@@ -1950,233 +1861,30 @@ function memoryOrigin(idempotencyKey) {
|
|
|
1950
1861
|
if (idempotencyKey?.startsWith("tool:")) return "explicit";
|
|
1951
1862
|
return "other";
|
|
1952
1863
|
}
|
|
1953
|
-
function
|
|
1954
|
-
return scope === "personal" ? "private" : "public";
|
|
1955
|
-
}
|
|
1956
|
-
function personalMemoryRecord(row) {
|
|
1864
|
+
function toMemoryView(row) {
|
|
1957
1865
|
const memory = parseMemoryRow(row);
|
|
1958
1866
|
return {
|
|
1959
1867
|
...memory,
|
|
1960
1868
|
origin: memoryOrigin(row.idempotencyKey),
|
|
1961
1869
|
sourcePlatform: row.sourcePlatform,
|
|
1962
|
-
visibility:
|
|
1870
|
+
visibility: memory.scope
|
|
1963
1871
|
};
|
|
1964
1872
|
}
|
|
1965
|
-
function
|
|
1873
|
+
function cursorFilters(input) {
|
|
1966
1874
|
return {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
explicit: 0,
|
|
1972
|
-
knowledge: 0,
|
|
1973
|
-
personal: 0,
|
|
1974
|
-
preference: 0,
|
|
1975
|
-
procedure: 0,
|
|
1976
|
-
public: 0
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
function createPersonalMemoryCollection(db, scopes, options = {}) {
|
|
1980
|
-
const { privateScopes, publicScopes } = scopes;
|
|
1981
|
-
const allScopes = [...privateScopes, ...publicScopes];
|
|
1982
|
-
const getNowMs = () => options.now?.() ?? Date.now();
|
|
1983
|
-
function scopesForVisibility(visibility) {
|
|
1984
|
-
if (visibility === "private") return privateScopes;
|
|
1985
|
-
if (visibility === "public") return publicScopes;
|
|
1986
|
-
return allScopes;
|
|
1987
|
-
}
|
|
1988
|
-
return {
|
|
1989
|
-
async archive(id) {
|
|
1990
|
-
const memoryId = nonEmptyStringSchema3.parse(id);
|
|
1991
|
-
const nowMs = getNowMs();
|
|
1992
|
-
const predicate = activeVisiblePredicate({
|
|
1993
|
-
nowMs,
|
|
1994
|
-
scopes: privateScopes
|
|
1995
|
-
});
|
|
1996
|
-
if (!predicate) {
|
|
1997
|
-
throw new PersonalMemoryNotFoundError();
|
|
1998
|
-
}
|
|
1999
|
-
const updated = await db.update(juniorMemoryMemories).set({
|
|
2000
|
-
archivedAtMs: nowMs,
|
|
2001
|
-
archiveReason: "user_removed"
|
|
2002
|
-
}).where(and2(predicate, eq2(juniorMemoryMemories.id, memoryId))).returning();
|
|
2003
|
-
if (!updated[0]) {
|
|
2004
|
-
throw new PersonalMemoryNotFoundError();
|
|
2005
|
-
}
|
|
2006
|
-
await db.delete(juniorMemoryEmbeddings).where(eq2(juniorMemoryEmbeddings.memoryId, memoryId));
|
|
2007
|
-
return parseMemoryRow(updated[0]);
|
|
2008
|
-
},
|
|
2009
|
-
async get(id) {
|
|
2010
|
-
const memoryId = nonEmptyStringSchema3.parse(id);
|
|
2011
|
-
const nowMs = getNowMs();
|
|
2012
|
-
const predicate = activeVisiblePredicate({ nowMs, scopes: allScopes });
|
|
2013
|
-
if (!predicate) {
|
|
2014
|
-
throw new PersonalMemoryNotFoundError();
|
|
2015
|
-
}
|
|
2016
|
-
const rows = await db.select().from(juniorMemoryMemories).where(and2(predicate, eq2(juniorMemoryMemories.id, memoryId))).limit(1);
|
|
2017
|
-
if (!rows[0]) {
|
|
2018
|
-
throw new PersonalMemoryNotFoundError();
|
|
2019
|
-
}
|
|
2020
|
-
return personalMemoryRecord(rows[0]);
|
|
2021
|
-
},
|
|
2022
|
-
async list(input) {
|
|
2023
|
-
input = personalMemoryPageInputSchema.parse(input);
|
|
2024
|
-
const nowMs = getNowMs();
|
|
2025
|
-
const scopes2 = scopesForVisibility(input.visibility);
|
|
2026
|
-
await archiveExpiredMemoryBatch({ db, nowMs, scopes: scopes2 });
|
|
2027
|
-
const active = activeVisiblePredicate({ nowMs, scopes: scopes2 });
|
|
2028
|
-
if (!active) {
|
|
2029
|
-
return { memories: [] };
|
|
2030
|
-
}
|
|
2031
|
-
const cursor = input.cursor ? or2(
|
|
2032
|
-
lt(juniorMemoryMemories.createdAtMs, input.cursor.createdAtMs),
|
|
2033
|
-
and2(
|
|
2034
|
-
eq2(juniorMemoryMemories.createdAtMs, input.cursor.createdAtMs),
|
|
2035
|
-
gt2(juniorMemoryMemories.id, input.cursor.id)
|
|
2036
|
-
)
|
|
2037
|
-
) : void 0;
|
|
2038
|
-
const terms = input.query ? searchTerms(input.query) : [];
|
|
2039
|
-
const search = input.query === void 0 ? void 0 : terms.length === 0 ? sql3`false` : or2(
|
|
2040
|
-
...terms.map(
|
|
2041
|
-
(term) => ilike(juniorMemoryMemories.content, `%${term}%`)
|
|
2042
|
-
)
|
|
2043
|
-
);
|
|
2044
|
-
const kind = input.kind ? eq2(juniorMemoryMemories.kind, input.kind) : void 0;
|
|
2045
|
-
const origin = input.origin === "automatic" ? like2(juniorMemoryMemories.idempotencyKey, "session:%") : input.origin === "explicit" ? like2(juniorMemoryMemories.idempotencyKey, "tool:%") : void 0;
|
|
2046
|
-
const rows = await db.select().from(juniorMemoryMemories).where(and2(active, cursor, search, kind, origin)).orderBy(
|
|
2047
|
-
desc2(juniorMemoryMemories.createdAtMs),
|
|
2048
|
-
asc2(juniorMemoryMemories.id)
|
|
2049
|
-
).limit(input.limit + 1);
|
|
2050
|
-
const hasNextPage = rows.length > input.limit;
|
|
2051
|
-
const memories = rows.slice(0, input.limit).map(personalMemoryRecord);
|
|
2052
|
-
const last = memories.at(-1);
|
|
2053
|
-
return {
|
|
2054
|
-
memories,
|
|
2055
|
-
...hasNextPage && last ? {
|
|
2056
|
-
nextCursor: {
|
|
2057
|
-
createdAtMs: last.createdAtMs,
|
|
2058
|
-
id: last.id
|
|
2059
|
-
}
|
|
2060
|
-
} : void 0
|
|
2061
|
-
};
|
|
2062
|
-
},
|
|
2063
|
-
async stats() {
|
|
2064
|
-
const nowMs = getNowMs();
|
|
2065
|
-
await archiveExpiredMemoryBatch({ db, nowMs, scopes: allScopes });
|
|
2066
|
-
const active = activeVisiblePredicate({ nowMs, scopes: allScopes });
|
|
2067
|
-
if (!active) {
|
|
2068
|
-
return emptyStats();
|
|
2069
|
-
}
|
|
2070
|
-
const [counts] = await db.select({
|
|
2071
|
-
active: sql3`count(*)`.mapWith(Number),
|
|
2072
|
-
automatic: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'session:%')`.mapWith(
|
|
2073
|
-
Number
|
|
2074
|
-
),
|
|
2075
|
-
createdThirtyDays: sql3`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${nowMs - 30 * 24 * 60 * 60 * 1e3})`.mapWith(
|
|
2076
|
-
Number
|
|
2077
|
-
),
|
|
2078
|
-
embedded: sql3`count(${juniorMemoryEmbeddings.memoryId})`.mapWith(
|
|
2079
|
-
Number
|
|
2080
|
-
),
|
|
2081
|
-
explicit: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'tool:%')`.mapWith(
|
|
2082
|
-
Number
|
|
2083
|
-
),
|
|
2084
|
-
knowledge: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'knowledge')`.mapWith(
|
|
2085
|
-
Number
|
|
2086
|
-
),
|
|
2087
|
-
personal: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
|
|
2088
|
-
Number
|
|
2089
|
-
),
|
|
2090
|
-
preference: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'preference')`.mapWith(
|
|
2091
|
-
Number
|
|
2092
|
-
),
|
|
2093
|
-
procedure: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
|
|
2094
|
-
Number
|
|
2095
|
-
),
|
|
2096
|
-
public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
|
|
2097
|
-
Number
|
|
2098
|
-
)
|
|
2099
|
-
}).from(juniorMemoryMemories).leftJoin(
|
|
2100
|
-
juniorMemoryEmbeddings,
|
|
2101
|
-
eq2(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
2102
|
-
).where(active);
|
|
2103
|
-
return {
|
|
2104
|
-
active: counts?.active ?? 0,
|
|
2105
|
-
automatic: counts?.automatic ?? 0,
|
|
2106
|
-
createdThirtyDays: counts?.createdThirtyDays ?? 0,
|
|
2107
|
-
embedded: counts?.embedded ?? 0,
|
|
2108
|
-
explicit: counts?.explicit ?? 0,
|
|
2109
|
-
knowledge: counts?.knowledge ?? 0,
|
|
2110
|
-
personal: counts?.personal ?? 0,
|
|
2111
|
-
preference: counts?.preference ?? 0,
|
|
2112
|
-
procedure: counts?.procedure ?? 0,
|
|
2113
|
-
public: counts?.public ?? 0
|
|
2114
|
-
};
|
|
2115
|
-
},
|
|
2116
|
-
async timeline(input) {
|
|
2117
|
-
input = personalMemoryTimelineInputSchema.parse(input);
|
|
2118
|
-
const todayMs = Date.parse(`${utcDate(getNowMs())}T00:00:00.000Z`);
|
|
2119
|
-
const startMs = todayMs - (input.days - 1) * DAY_MS;
|
|
2120
|
-
const ownership = scopePredicate(allScopes);
|
|
2121
|
-
if (!ownership) {
|
|
2122
|
-
return Array.from({ length: input.days }, (_, index2) => ({
|
|
2123
|
-
date: utcDate(startMs + index2 * DAY_MS),
|
|
2124
|
-
personal: 0,
|
|
2125
|
-
public: 0
|
|
2126
|
-
}));
|
|
2127
|
-
}
|
|
2128
|
-
const rows = await db.select({
|
|
2129
|
-
date: sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`.as(
|
|
2130
|
-
"date"
|
|
2131
|
-
),
|
|
2132
|
-
personal: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
|
|
2133
|
-
Number
|
|
2134
|
-
),
|
|
2135
|
-
public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
|
|
2136
|
-
Number
|
|
2137
|
-
)
|
|
2138
|
-
}).from(juniorMemoryMemories).where(
|
|
2139
|
-
and2(ownership, gt2(juniorMemoryMemories.createdAtMs, startMs - 1))
|
|
2140
|
-
).groupBy(
|
|
2141
|
-
sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`
|
|
2142
|
-
);
|
|
2143
|
-
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2144
|
-
return Array.from({ length: input.days }, (_, index2) => {
|
|
2145
|
-
const date = utcDate(startMs + index2 * DAY_MS);
|
|
2146
|
-
const row = byDate.get(date);
|
|
2147
|
-
return {
|
|
2148
|
-
date,
|
|
2149
|
-
personal: row?.personal ?? 0,
|
|
2150
|
-
public: row?.public ?? 0
|
|
2151
|
-
};
|
|
2152
|
-
});
|
|
2153
|
-
}
|
|
1875
|
+
kind: input.kind,
|
|
1876
|
+
origin: input.origin,
|
|
1877
|
+
query: input.query?.trim() || void 0,
|
|
1878
|
+
visibility: input.visibility
|
|
2154
1879
|
};
|
|
2155
1880
|
}
|
|
2156
|
-
|
|
2157
|
-
// src/personal.ts
|
|
2158
|
-
var cursorSchema = z5.object({
|
|
2159
|
-
createdAtMs: z5.number().finite(),
|
|
2160
|
-
id: z5.string().min(1),
|
|
2161
|
-
kind: z5.enum(["preference", "procedure", "knowledge"]).optional(),
|
|
2162
|
-
origin: z5.enum(["automatic", "explicit"]).optional(),
|
|
2163
|
-
query: z5.string().max(200).optional(),
|
|
2164
|
-
version: z5.literal(1),
|
|
2165
|
-
visibility: z5.enum(["private", "public"]).optional()
|
|
2166
|
-
}).strict();
|
|
2167
|
-
var InvalidMemoryCursorError = class extends Error {
|
|
2168
|
-
constructor() {
|
|
2169
|
-
super("Memory cursor is invalid.");
|
|
2170
|
-
this.name = "InvalidMemoryCursorError";
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2173
|
-
function decodeCursor(value, input) {
|
|
1881
|
+
function decodeCursor(value, filters) {
|
|
2174
1882
|
if (!value) return void 0;
|
|
2175
1883
|
try {
|
|
2176
1884
|
const parsed = cursorSchema.parse(
|
|
2177
1885
|
JSON.parse(Buffer.from(value, "base64url").toString("utf8"))
|
|
2178
1886
|
);
|
|
2179
|
-
if (parsed.query !==
|
|
1887
|
+
if (parsed.query !== filters.query || parsed.kind !== filters.kind || parsed.origin !== filters.origin || parsed.visibility !== filters.visibility) {
|
|
2180
1888
|
throw new InvalidMemoryCursorError();
|
|
2181
1889
|
}
|
|
2182
1890
|
return { createdAtMs: parsed.createdAtMs, id: parsed.id };
|
|
@@ -2184,106 +1892,190 @@ function decodeCursor(value, input) {
|
|
|
2184
1892
|
throw new InvalidMemoryCursorError();
|
|
2185
1893
|
}
|
|
2186
1894
|
}
|
|
2187
|
-
function encodeCursor(
|
|
1895
|
+
function encodeCursor(createdBefore, filters) {
|
|
2188
1896
|
return Buffer.from(
|
|
2189
|
-
JSON.stringify({
|
|
2190
|
-
...cursor,
|
|
2191
|
-
...input.query ? { query: input.query } : void 0,
|
|
2192
|
-
...input.kind ? { kind: input.kind } : void 0,
|
|
2193
|
-
...input.origin ? { origin: input.origin } : void 0,
|
|
2194
|
-
...input.visibility ? { visibility: input.visibility } : void 0,
|
|
2195
|
-
version: 1
|
|
2196
|
-
}),
|
|
1897
|
+
JSON.stringify({ ...createdBefore, ...filters, version: 1 }),
|
|
2197
1898
|
"utf8"
|
|
2198
1899
|
).toString("base64url");
|
|
2199
1900
|
}
|
|
2200
|
-
function
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
1901
|
+
async function archiveMemory(db, userId, id) {
|
|
1902
|
+
const memoryId = nonEmptyStringSchema3.parse(id);
|
|
1903
|
+
const nowMs = Date.now();
|
|
1904
|
+
const updated = await db.update(juniorMemoryMemories).set({
|
|
1905
|
+
archivedAtMs: nowMs,
|
|
1906
|
+
archiveReason: "user_removed"
|
|
1907
|
+
}).where(
|
|
1908
|
+
and2(
|
|
1909
|
+
activeMemoryPredicate(userId, nowMs, "private"),
|
|
1910
|
+
eq2(juniorMemoryMemories.id, memoryId)
|
|
1911
|
+
)
|
|
1912
|
+
).returning();
|
|
1913
|
+
if (!updated[0]) throw new MemoryNotFoundError();
|
|
1914
|
+
await db.delete(juniorMemoryEmbeddings).where(eq2(juniorMemoryEmbeddings.memoryId, memoryId));
|
|
1915
|
+
return parseMemoryRow(updated[0]);
|
|
1916
|
+
}
|
|
1917
|
+
async function getMemory(db, userId, id) {
|
|
1918
|
+
const memoryId = nonEmptyStringSchema3.parse(id);
|
|
1919
|
+
const nowMs = Date.now();
|
|
1920
|
+
const rows = await db.select().from(juniorMemoryMemories).where(
|
|
1921
|
+
and2(
|
|
1922
|
+
activeMemoryPredicate(userId, nowMs),
|
|
1923
|
+
eq2(juniorMemoryMemories.id, memoryId)
|
|
1924
|
+
)
|
|
1925
|
+
).limit(1);
|
|
1926
|
+
if (!rows[0]) throw new MemoryNotFoundError();
|
|
1927
|
+
return toMemoryView(rows[0]);
|
|
1928
|
+
}
|
|
1929
|
+
async function listMemories(db, userId, input) {
|
|
1930
|
+
input = pageInputSchema.parse(input);
|
|
1931
|
+
const filters = cursorFilters(input);
|
|
1932
|
+
const cursor = decodeCursor(input.cursor, filters);
|
|
1933
|
+
const active = activeMemoryPredicate(userId, Date.now(), input.visibility);
|
|
1934
|
+
const createdBefore = cursor ? or2(
|
|
1935
|
+
lt(juniorMemoryMemories.createdAtMs, cursor.createdAtMs),
|
|
1936
|
+
and2(
|
|
1937
|
+
eq2(juniorMemoryMemories.createdAtMs, cursor.createdAtMs),
|
|
1938
|
+
gt2(juniorMemoryMemories.id, cursor.id)
|
|
1939
|
+
)
|
|
1940
|
+
) : void 0;
|
|
1941
|
+
const terms = input.query ? searchTerms(input.query) : [];
|
|
1942
|
+
const search = input.query === void 0 ? void 0 : terms.length === 0 ? sql3`false` : or2(
|
|
1943
|
+
...terms.map(
|
|
1944
|
+
(term) => ilike(juniorMemoryMemories.content, `%${term}%`)
|
|
1945
|
+
)
|
|
2204
1946
|
);
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
1947
|
+
const kind = input.kind ? eq2(juniorMemoryMemories.kind, input.kind) : void 0;
|
|
1948
|
+
const origin = input.origin === "automatic" ? like2(juniorMemoryMemories.idempotencyKey, "session:%") : input.origin === "explicit" ? like2(juniorMemoryMemories.idempotencyKey, "tool:%") : void 0;
|
|
1949
|
+
const rows = await db.select().from(juniorMemoryMemories).where(and2(active, createdBefore, search, kind, origin)).orderBy(
|
|
1950
|
+
desc2(juniorMemoryMemories.createdAtMs),
|
|
1951
|
+
asc2(juniorMemoryMemories.id)
|
|
1952
|
+
).limit(input.limit + 1);
|
|
1953
|
+
const memories = rows.slice(0, input.limit).map(toMemoryView);
|
|
1954
|
+
const last = memories.at(-1);
|
|
1955
|
+
if (rows.length <= input.limit || !last) return { memories };
|
|
1956
|
+
const next = { createdAtMs: last.createdAtMs, id: last.id };
|
|
1957
|
+
return { memories, nextCursor: encodeCursor(next, filters) };
|
|
1958
|
+
}
|
|
1959
|
+
async function getMemoryStats(db, userId) {
|
|
1960
|
+
const nowMs = Date.now();
|
|
1961
|
+
const [counts] = await db.select({
|
|
1962
|
+
active: sql3`count(*)`.mapWith(Number),
|
|
1963
|
+
automatic: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'session:%')`.mapWith(
|
|
1964
|
+
Number
|
|
1965
|
+
),
|
|
1966
|
+
createdThirtyDays: sql3`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${nowMs - 30 * DAY_MS})`.mapWith(
|
|
1967
|
+
Number
|
|
1968
|
+
),
|
|
1969
|
+
embedded: sql3`count(${juniorMemoryEmbeddings.memoryId})`.mapWith(
|
|
1970
|
+
Number
|
|
1971
|
+
),
|
|
1972
|
+
explicit: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'tool:%')`.mapWith(
|
|
1973
|
+
Number
|
|
1974
|
+
),
|
|
1975
|
+
knowledge: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'knowledge')`.mapWith(
|
|
1976
|
+
Number
|
|
1977
|
+
),
|
|
1978
|
+
private: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
1979
|
+
Number
|
|
1980
|
+
),
|
|
1981
|
+
preference: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'preference')`.mapWith(
|
|
1982
|
+
Number
|
|
1983
|
+
),
|
|
1984
|
+
procedure: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
|
|
1985
|
+
Number
|
|
1986
|
+
),
|
|
1987
|
+
public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
1988
|
+
Number
|
|
1989
|
+
)
|
|
1990
|
+
}).from(juniorMemoryMemories).leftJoin(
|
|
1991
|
+
juniorMemoryEmbeddings,
|
|
1992
|
+
eq2(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
|
|
1993
|
+
).where(activeMemoryPredicate(userId, nowMs));
|
|
1994
|
+
if (!counts) throw new Error("Memory stats query returned no row.");
|
|
1995
|
+
return counts;
|
|
1996
|
+
}
|
|
1997
|
+
async function getMemoryTimeline(db, userId, days) {
|
|
1998
|
+
days = timelineDaysSchema.parse(days);
|
|
1999
|
+
const todayMs = Date.parse(`${utcDate(Date.now())}T00:00:00.000Z`);
|
|
2000
|
+
const startMs = todayMs - (days - 1) * DAY_MS;
|
|
2001
|
+
const rows = await db.select({
|
|
2002
|
+
date: sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`.as(
|
|
2003
|
+
"date"
|
|
2004
|
+
),
|
|
2005
|
+
private: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
2006
|
+
Number
|
|
2007
|
+
),
|
|
2008
|
+
public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
2009
|
+
Number
|
|
2010
|
+
)
|
|
2011
|
+
}).from(juniorMemoryMemories).where(
|
|
2012
|
+
and2(
|
|
2013
|
+
visibleScopePredicate2(userId),
|
|
2014
|
+
gt2(juniorMemoryMemories.createdAtMs, startMs - 1)
|
|
2015
|
+
)
|
|
2016
|
+
).groupBy(
|
|
2017
|
+
sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`
|
|
2018
|
+
);
|
|
2019
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2020
|
+
return Array.from({ length: days }, (_, index2) => {
|
|
2021
|
+
const date = utcDate(startMs + index2 * DAY_MS);
|
|
2022
|
+
const row = byDate.get(date);
|
|
2023
|
+
return {
|
|
2024
|
+
date,
|
|
2025
|
+
private: row?.private ?? 0,
|
|
2026
|
+
public: row?.public ?? 0
|
|
2027
|
+
};
|
|
2028
|
+
});
|
|
2237
2029
|
}
|
|
2238
2030
|
|
|
2239
2031
|
// src/api.ts
|
|
2240
|
-
var memoryApiSchema =
|
|
2241
|
-
content:
|
|
2242
|
-
createdAt:
|
|
2243
|
-
expiresAt:
|
|
2244
|
-
id:
|
|
2245
|
-
kind:
|
|
2246
|
-
observedAt:
|
|
2247
|
-
origin:
|
|
2248
|
-
sourcePlatform:
|
|
2249
|
-
visibility:
|
|
2032
|
+
var memoryApiSchema = z5.object({
|
|
2033
|
+
content: z5.string().min(1),
|
|
2034
|
+
createdAt: z5.iso.datetime(),
|
|
2035
|
+
expiresAt: z5.iso.datetime().optional(),
|
|
2036
|
+
id: z5.string().min(1),
|
|
2037
|
+
kind: z5.enum(["preference", "procedure", "knowledge"]),
|
|
2038
|
+
observedAt: z5.iso.datetime(),
|
|
2039
|
+
origin: z5.enum(["automatic", "explicit", "other"]),
|
|
2040
|
+
sourcePlatform: z5.enum(MEMORY_SOURCE_PLATFORMS),
|
|
2041
|
+
visibility: z5.enum(["private", "public"])
|
|
2250
2042
|
}).strict();
|
|
2251
|
-
var memoryListResponseSchema =
|
|
2252
|
-
memories:
|
|
2253
|
-
nextCursor:
|
|
2043
|
+
var memoryListResponseSchema = z5.object({
|
|
2044
|
+
memories: z5.array(memoryApiSchema),
|
|
2045
|
+
nextCursor: z5.string().min(1).optional()
|
|
2254
2046
|
}).strict();
|
|
2255
|
-
var memoryDashboardDaySchema =
|
|
2256
|
-
date:
|
|
2257
|
-
personal:
|
|
2258
|
-
public:
|
|
2047
|
+
var memoryDashboardDaySchema = z5.object({
|
|
2048
|
+
date: z5.iso.date(),
|
|
2049
|
+
personal: z5.number().int().min(0),
|
|
2050
|
+
public: z5.number().int().min(0)
|
|
2259
2051
|
}).strict();
|
|
2260
|
-
var memoryCostDaySchema =
|
|
2261
|
-
costUsd:
|
|
2262
|
-
date:
|
|
2263
|
-
events:
|
|
2052
|
+
var memoryCostDaySchema = z5.object({
|
|
2053
|
+
costUsd: z5.number().finite().nonnegative(),
|
|
2054
|
+
date: z5.iso.date(),
|
|
2055
|
+
events: z5.number().int().min(0)
|
|
2264
2056
|
}).strict();
|
|
2265
|
-
var memoryDashboardResponseSchema =
|
|
2266
|
-
days:
|
|
2267
|
-
extractionDays:
|
|
2268
|
-
generatedAt:
|
|
2269
|
-
recallDays:
|
|
2270
|
-
stats:
|
|
2271
|
-
active:
|
|
2272
|
-
automatic:
|
|
2273
|
-
createdThirtyDays:
|
|
2274
|
-
embedded:
|
|
2275
|
-
explicit:
|
|
2276
|
-
knowledge:
|
|
2277
|
-
personal:
|
|
2278
|
-
preference:
|
|
2279
|
-
procedure:
|
|
2280
|
-
public:
|
|
2057
|
+
var memoryDashboardResponseSchema = z5.object({
|
|
2058
|
+
days: z5.array(memoryDashboardDaySchema).length(90),
|
|
2059
|
+
extractionDays: z5.array(memoryCostDaySchema).length(90),
|
|
2060
|
+
generatedAt: z5.iso.datetime(),
|
|
2061
|
+
recallDays: z5.array(memoryCostDaySchema).length(90),
|
|
2062
|
+
stats: z5.object({
|
|
2063
|
+
active: z5.number().int().min(0),
|
|
2064
|
+
automatic: z5.number().int().min(0),
|
|
2065
|
+
createdThirtyDays: z5.number().int().min(0),
|
|
2066
|
+
embedded: z5.number().int().min(0),
|
|
2067
|
+
explicit: z5.number().int().min(0),
|
|
2068
|
+
knowledge: z5.number().int().min(0),
|
|
2069
|
+
personal: z5.number().int().min(0),
|
|
2070
|
+
preference: z5.number().int().min(0),
|
|
2071
|
+
procedure: z5.number().int().min(0),
|
|
2072
|
+
public: z5.number().int().min(0)
|
|
2281
2073
|
}).strict()
|
|
2282
2074
|
}).strict();
|
|
2283
|
-
var memoryListQuerySchema =
|
|
2284
|
-
cursor:
|
|
2285
|
-
limit:
|
|
2286
|
-
q:
|
|
2075
|
+
var memoryListQuerySchema = z5.object({
|
|
2076
|
+
cursor: z5.string().min(1).max(1e3).optional(),
|
|
2077
|
+
limit: z5.coerce.number().int().min(1).max(50).default(25),
|
|
2078
|
+
q: z5.string().trim().max(200).optional()
|
|
2287
2079
|
}).strict();
|
|
2288
2080
|
function json(body, status = 200) {
|
|
2289
2081
|
return Response.json(body, {
|
|
@@ -2329,14 +2121,14 @@ function createMemoryApi(options) {
|
|
|
2329
2121
|
if (!isRead && !(memoryPath && request.method === "DELETE")) {
|
|
2330
2122
|
return json({ error: "Method not allowed." }, 405);
|
|
2331
2123
|
}
|
|
2332
|
-
const
|
|
2333
|
-
if (!
|
|
2334
|
-
const
|
|
2124
|
+
const viewer = await options.users.resolve(email);
|
|
2125
|
+
if (!viewer) return json({ error: "Authentication required." }, 401);
|
|
2126
|
+
const userId = viewer.id;
|
|
2335
2127
|
try {
|
|
2336
2128
|
if (isDashboard && isRead) {
|
|
2337
2129
|
const [stats, days, extractionDays, recallDays] = await Promise.all([
|
|
2338
|
-
|
|
2339
|
-
|
|
2130
|
+
getMemoryStats(options.db, userId),
|
|
2131
|
+
getMemoryTimeline(options.db, userId, 90),
|
|
2340
2132
|
options.eventStats.costsByDay({
|
|
2341
2133
|
days: 90,
|
|
2342
2134
|
eventName: "memories_captured"
|
|
@@ -2346,12 +2138,16 @@ function createMemoryApi(options) {
|
|
|
2346
2138
|
eventName: "memories_recalled"
|
|
2347
2139
|
})
|
|
2348
2140
|
]);
|
|
2141
|
+
const { private: personal, ...dashboardStats } = stats;
|
|
2349
2142
|
const body = memoryDashboardResponseSchema.parse({
|
|
2350
|
-
days,
|
|
2143
|
+
days: days.map(({ private: personal2, ...day }) => ({
|
|
2144
|
+
...day,
|
|
2145
|
+
personal: personal2
|
|
2146
|
+
})),
|
|
2351
2147
|
extractionDays,
|
|
2352
2148
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2353
2149
|
recallDays,
|
|
2354
|
-
stats
|
|
2150
|
+
stats: { ...dashboardStats, personal }
|
|
2355
2151
|
});
|
|
2356
2152
|
return request.method === "HEAD" ? new Response(null, {
|
|
2357
2153
|
headers: { "cache-control": "no-store" },
|
|
@@ -2364,7 +2160,7 @@ function createMemoryApi(options) {
|
|
|
2364
2160
|
limit: url.searchParams.get("limit") ?? void 0,
|
|
2365
2161
|
q: url.searchParams.get("q") ?? void 0
|
|
2366
2162
|
});
|
|
2367
|
-
const page = await
|
|
2163
|
+
const page = await listMemories(options.db, userId, {
|
|
2368
2164
|
cursor: query.cursor,
|
|
2369
2165
|
limit: query.limit,
|
|
2370
2166
|
...query.q ? { query: query.q } : void 0
|
|
@@ -2379,26 +2175,30 @@ function createMemoryApi(options) {
|
|
|
2379
2175
|
}) : json(body);
|
|
2380
2176
|
}
|
|
2381
2177
|
if (memoryPath && isRead) {
|
|
2382
|
-
const memory =
|
|
2383
|
-
|
|
2178
|
+
const memory = await getMemory(
|
|
2179
|
+
options.db,
|
|
2180
|
+
userId,
|
|
2181
|
+
decodeURIComponent(memoryPath[1])
|
|
2384
2182
|
);
|
|
2183
|
+
const body = memoryApiSchema.parse(apiMemory(memory));
|
|
2385
2184
|
return request.method === "HEAD" ? new Response(null, {
|
|
2386
2185
|
headers: { "cache-control": "no-store" },
|
|
2387
2186
|
status: 200
|
|
2388
|
-
}) : json(
|
|
2187
|
+
}) : json(body);
|
|
2389
2188
|
}
|
|
2390
2189
|
if (memoryPath && request.method === "DELETE") {
|
|
2391
|
-
|
|
2190
|
+
const id = decodeURIComponent(memoryPath[1]);
|
|
2191
|
+
await archiveMemory(options.db, userId, id);
|
|
2392
2192
|
return new Response(null, {
|
|
2393
2193
|
headers: { "cache-control": "no-store" },
|
|
2394
2194
|
status: 204
|
|
2395
2195
|
});
|
|
2396
2196
|
}
|
|
2397
2197
|
} catch (error) {
|
|
2398
|
-
if (error instanceof
|
|
2198
|
+
if (error instanceof z5.ZodError || error instanceof InvalidMemoryCursorError) {
|
|
2399
2199
|
return json({ error: "Invalid memory request." }, 400);
|
|
2400
2200
|
}
|
|
2401
|
-
if (error instanceof
|
|
2201
|
+
if (error instanceof MemoryNotFoundError) {
|
|
2402
2202
|
return json({ error: error.message }, 404);
|
|
2403
2203
|
}
|
|
2404
2204
|
throw error;
|
|
@@ -2410,7 +2210,7 @@ function createMemoryApi(options) {
|
|
|
2410
2210
|
|
|
2411
2211
|
// src/cli/search.ts
|
|
2412
2212
|
import { InvalidArgumentError, Option } from "commander";
|
|
2413
|
-
import { and as and3, desc as desc3, eq as eq3, gt as gt3, ilike as ilike2, isNull as
|
|
2213
|
+
import { and as and3, desc as desc3, eq as eq3, gt as gt3, ilike as ilike2, isNull as isNull3, or as or3 } from "drizzle-orm";
|
|
2414
2214
|
|
|
2415
2215
|
// src/cli/format.ts
|
|
2416
2216
|
function formatDate(ms) {
|
|
@@ -2453,15 +2253,15 @@ async function runSearch(ctx, queryParts, options) {
|
|
|
2453
2253
|
];
|
|
2454
2254
|
const db = ctx.db;
|
|
2455
2255
|
const activeExpirationPredicate = or3(
|
|
2456
|
-
|
|
2256
|
+
isNull3(juniorMemoryMemories.expiresAtMs),
|
|
2457
2257
|
gt3(juniorMemoryMemories.expiresAtMs, nowMs)
|
|
2458
2258
|
);
|
|
2459
2259
|
const predicates = [
|
|
2460
2260
|
eq3(juniorMemoryMemories.scope, options.scope),
|
|
2461
2261
|
eq3(juniorMemoryMemories.scopeKey, options.scopeKey),
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2262
|
+
isNull3(juniorMemoryMemories.archivedAtMs),
|
|
2263
|
+
isNull3(juniorMemoryMemories.supersededAtMs),
|
|
2264
|
+
isNull3(juniorMemoryMemories.supersededById)
|
|
2465
2265
|
];
|
|
2466
2266
|
if (activeExpirationPredicate) {
|
|
2467
2267
|
predicates.push(activeExpirationPredicate);
|
|
@@ -2542,11 +2342,11 @@ import { Type } from "@sinclair/typebox";
|
|
|
2542
2342
|
import { Value } from "@sinclair/typebox/value";
|
|
2543
2343
|
import {
|
|
2544
2344
|
definePluginTool,
|
|
2545
|
-
getSourceKey,
|
|
2345
|
+
getSourceKey as getSourceKey2,
|
|
2546
2346
|
PluginToolInputError,
|
|
2547
2347
|
pluginToolOutputSchema
|
|
2548
2348
|
} from "@sentry/junior-plugin-api";
|
|
2549
|
-
import { z as
|
|
2349
|
+
import { z as z6 } from "zod";
|
|
2550
2350
|
var MAX_TOOL_CONTENT_CHARS = 4e3;
|
|
2551
2351
|
var DEFAULT_RESULT_LIMIT = 20;
|
|
2552
2352
|
var DEFAULT_SEARCH_LIMIT2 = 10;
|
|
@@ -2558,8 +2358,8 @@ var KNOWN_TOOL_INPUT_ERROR_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
2558
2358
|
"Memory id is required.",
|
|
2559
2359
|
"Memory was not found in the current context.",
|
|
2560
2360
|
"Memory id prefix is ambiguous.",
|
|
2561
|
-
"
|
|
2562
|
-
"User
|
|
2361
|
+
"Private memory requires a User.",
|
|
2362
|
+
"User memory requires a User."
|
|
2563
2363
|
]);
|
|
2564
2364
|
function throwToolInputError(message) {
|
|
2565
2365
|
throw new PluginToolInputError(message);
|
|
@@ -2573,15 +2373,18 @@ function asToolInputError(error) {
|
|
|
2573
2373
|
}
|
|
2574
2374
|
throw error;
|
|
2575
2375
|
}
|
|
2576
|
-
function memoryRuntimeContext(context) {
|
|
2376
|
+
async function memoryRuntimeContext(context) {
|
|
2377
|
+
const actorUser = (await context.users.resolveActor())?.user;
|
|
2577
2378
|
return memoryRuntimeContextSchema.parse({
|
|
2578
2379
|
...context.conversationId ? { conversationId: context.conversationId } : void 0,
|
|
2579
2380
|
...context.actor ? { actor: context.actor } : void 0,
|
|
2580
|
-
|
|
2381
|
+
...context.locationId ? { locationId: context.locationId } : void 0,
|
|
2382
|
+
source: context.source,
|
|
2383
|
+
...actorUser ? { userId: actorUser.id } : void 0
|
|
2581
2384
|
});
|
|
2582
2385
|
}
|
|
2583
|
-
function memoryStore(context, options = {}) {
|
|
2584
|
-
return createMemoryStore(context.db,
|
|
2386
|
+
function memoryStore(context, runtimeContext, options = {}) {
|
|
2387
|
+
return createMemoryStore(context.db, runtimeContext, {
|
|
2585
2388
|
embedder: context.embedder,
|
|
2586
2389
|
...options.supersessionDecider ? { supersessionDecider: options.supersessionDecider } : void 0
|
|
2587
2390
|
});
|
|
@@ -2673,23 +2476,23 @@ function requireMemoryContent(value) {
|
|
|
2673
2476
|
}
|
|
2674
2477
|
return value;
|
|
2675
2478
|
}
|
|
2676
|
-
var createMemoryInputSchema2 =
|
|
2677
|
-
content:
|
|
2678
|
-
"Self-contained
|
|
2479
|
+
var createMemoryInputSchema2 = z6.object({
|
|
2480
|
+
content: z6.string().min(1).max(MAX_TOOL_CONTENT_CHARS).describe(
|
|
2481
|
+
"Self-contained memory candidate. Include the subject in natural language when it matters; do not rely on surrounding chat context."
|
|
2679
2482
|
),
|
|
2680
|
-
expires_at:
|
|
2483
|
+
expires_at: z6.string().min(1).describe(
|
|
2681
2484
|
'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".'
|
|
2682
2485
|
).optional()
|
|
2683
2486
|
}).strict();
|
|
2684
|
-
var removeMemoryInputSchema =
|
|
2685
|
-
id:
|
|
2487
|
+
var removeMemoryInputSchema = z6.object({
|
|
2488
|
+
id: z6.string().min(1).describe("Memory id or unambiguous short id prefix to remove.")
|
|
2686
2489
|
}).strict();
|
|
2687
|
-
var listMemoriesInputSchema2 =
|
|
2688
|
-
limit:
|
|
2490
|
+
var listMemoriesInputSchema2 = z6.object({
|
|
2491
|
+
limit: z6.number().min(1).max(50).describe("Maximum number of visible memories to return.").optional()
|
|
2689
2492
|
}).strict();
|
|
2690
|
-
var searchMemoriesInputSchema2 =
|
|
2691
|
-
query:
|
|
2692
|
-
limit:
|
|
2493
|
+
var searchMemoriesInputSchema2 = z6.object({
|
|
2494
|
+
query: z6.string().min(1).describe("Search query for visible memory content."),
|
|
2495
|
+
limit: z6.number().min(1).max(50).describe("Maximum number of matching memories to return.").optional()
|
|
2693
2496
|
}).strict();
|
|
2694
2497
|
var memoryToolProjectionSchema = Type.Object(
|
|
2695
2498
|
{
|
|
@@ -2701,25 +2504,25 @@ var memoryToolProjectionSchema = Type.Object(
|
|
|
2701
2504
|
},
|
|
2702
2505
|
{ additionalProperties: false }
|
|
2703
2506
|
);
|
|
2704
|
-
var memoryProjectionOutputSchema =
|
|
2705
|
-
id:
|
|
2706
|
-
content:
|
|
2707
|
-
createdAtMs:
|
|
2708
|
-
observedAtMs:
|
|
2709
|
-
expiresAtMs:
|
|
2507
|
+
var memoryProjectionOutputSchema = z6.object({
|
|
2508
|
+
id: z6.string(),
|
|
2509
|
+
content: z6.string(),
|
|
2510
|
+
createdAtMs: z6.number(),
|
|
2511
|
+
observedAtMs: z6.number(),
|
|
2512
|
+
expiresAtMs: z6.number().optional()
|
|
2710
2513
|
});
|
|
2711
2514
|
var memoryCreateOutputSchema = pluginToolOutputSchema.extend({
|
|
2712
|
-
target:
|
|
2713
|
-
created:
|
|
2515
|
+
target: z6.string(),
|
|
2516
|
+
created: z6.boolean(),
|
|
2714
2517
|
memory: memoryProjectionOutputSchema
|
|
2715
2518
|
});
|
|
2716
2519
|
var memorySingleOutputSchema = pluginToolOutputSchema.extend({
|
|
2717
|
-
target:
|
|
2520
|
+
target: z6.string(),
|
|
2718
2521
|
memory: memoryProjectionOutputSchema
|
|
2719
2522
|
});
|
|
2720
2523
|
var memoryManyOutputSchema = pluginToolOutputSchema.extend({
|
|
2721
|
-
target:
|
|
2722
|
-
memories:
|
|
2524
|
+
target: z6.string(),
|
|
2525
|
+
memories: z6.array(memoryProjectionOutputSchema)
|
|
2723
2526
|
});
|
|
2724
2527
|
function parseMemoryToolInput(schema, input) {
|
|
2725
2528
|
const result = schema.safeParse(input);
|
|
@@ -2731,7 +2534,7 @@ function parseMemoryToolInput(schema, input) {
|
|
|
2731
2534
|
return result.data;
|
|
2732
2535
|
}
|
|
2733
2536
|
function sourceIdempotencyKey(context) {
|
|
2734
|
-
const sourceKey2 =
|
|
2537
|
+
const sourceKey2 = getSourceKey2(context.source);
|
|
2735
2538
|
if (!sourceKey2) {
|
|
2736
2539
|
throwToolInputError("Memory creation requires source message context.");
|
|
2737
2540
|
}
|
|
@@ -2775,7 +2578,7 @@ function createMemoryCreateTool(context) {
|
|
|
2775
2578
|
openWorldHint: false,
|
|
2776
2579
|
readOnlyHint: false
|
|
2777
2580
|
},
|
|
2778
|
-
description: "Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a
|
|
2581
|
+
description: "Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a fact. Do not use for ordinary statements like 'I prefer X', 'I use Y', or 'X goes before Y' unless the user also asks you to remember/store/save it; passive memory learning handles those after the visible reply. Pass one self-contained natural-language candidate preserving the user's explicit memory intent. Do not ask the user to rephrase ordinary first-person facts, and do not rewrite them into display-name or third-person wording. Do not include secrets, private personal details, medical/legal/financial/sensitive facts, or another person's personal preference, opinion, habit, identity, relationship, workflow, or private life. Junior sets access, Location, Source, and subject. The memory agent rewrites the content and sets the memory kind.",
|
|
2779
2582
|
executionMode: "sequential",
|
|
2780
2583
|
inputSchema: createMemoryInputSchema2,
|
|
2781
2584
|
outputSchema: memoryCreateOutputSchema,
|
|
@@ -2783,8 +2586,8 @@ function createMemoryCreateTool(context) {
|
|
|
2783
2586
|
const parsedInput = parseMemoryToolInput(createMemoryInputSchema2, input);
|
|
2784
2587
|
const toolCallId = requireToolCallId(options.toolCallId);
|
|
2785
2588
|
const requestedExpiresAtMs = parseExpiresAt(parsedInput.expires_at);
|
|
2786
|
-
const runtimeContext = memoryRuntimeContext(context);
|
|
2787
|
-
const store = memoryStore(context, {
|
|
2589
|
+
const runtimeContext = await memoryRuntimeContext(context);
|
|
2590
|
+
const store = memoryStore(context, runtimeContext, {
|
|
2788
2591
|
supersessionDecider: context.supersessionDecider
|
|
2789
2592
|
});
|
|
2790
2593
|
const review = await (async () => {
|
|
@@ -2853,15 +2656,16 @@ function createMemoryRemoveTool(context) {
|
|
|
2853
2656
|
openWorldHint: false,
|
|
2854
2657
|
readOnlyHint: false
|
|
2855
2658
|
},
|
|
2856
|
-
description: "Forget one memory
|
|
2659
|
+
description: "Forget one private memory owned by the current User. Public memories are read-only. Use only ids or short id prefixes returned by listMemories or searchMemories. Never remove memories by hidden Actor, provider, scope, or subject ids.",
|
|
2857
2660
|
executionMode: "sequential",
|
|
2858
2661
|
inputSchema: removeMemoryInputSchema,
|
|
2859
2662
|
outputSchema: memorySingleOutputSchema,
|
|
2860
2663
|
execute: async (input) => {
|
|
2861
2664
|
const parsedInput = parseMemoryToolInput(removeMemoryInputSchema, input);
|
|
2665
|
+
const runtimeContext = await memoryRuntimeContext(context);
|
|
2862
2666
|
const memory = await (async () => {
|
|
2863
2667
|
try {
|
|
2864
|
-
return await memoryStore(context).archiveMemory({
|
|
2668
|
+
return await memoryStore(context, runtimeContext).archiveMemory({
|
|
2865
2669
|
id: parsedInput.id,
|
|
2866
2670
|
reason: "tool_removed"
|
|
2867
2671
|
});
|
|
@@ -2888,7 +2692,8 @@ function createMemoryListTool(context) {
|
|
|
2888
2692
|
outputSchema: memoryManyOutputSchema,
|
|
2889
2693
|
execute: async (input) => {
|
|
2890
2694
|
const parsedInput = parseMemoryToolInput(listMemoriesInputSchema2, input);
|
|
2891
|
-
const
|
|
2695
|
+
const runtimeContext = await memoryRuntimeContext(context);
|
|
2696
|
+
const memories = await memoryStore(context, runtimeContext).listMemories({
|
|
2892
2697
|
limit: boundedLimit2(parsedInput.limit, DEFAULT_RESULT_LIMIT)
|
|
2893
2698
|
});
|
|
2894
2699
|
return memoryToolResult("listMemories", {
|
|
@@ -2899,7 +2704,7 @@ function createMemoryListTool(context) {
|
|
|
2899
2704
|
}
|
|
2900
2705
|
function createMemorySearchTool(context) {
|
|
2901
2706
|
return definePluginTool({
|
|
2902
|
-
description: "Search active memories visible in the current context. Use when the model needs targeted memory recall.
|
|
2707
|
+
description: "Search active memories visible in the current context. Use when the model needs targeted memory recall. Public memories are visible everywhere. Private memories belong to the current User.",
|
|
2903
2708
|
annotations: {
|
|
2904
2709
|
destructiveHint: false,
|
|
2905
2710
|
idempotentHint: true,
|
|
@@ -2913,7 +2718,11 @@ function createMemorySearchTool(context) {
|
|
|
2913
2718
|
searchMemoriesInputSchema2,
|
|
2914
2719
|
input
|
|
2915
2720
|
);
|
|
2916
|
-
const
|
|
2721
|
+
const runtimeContext = await memoryRuntimeContext(context);
|
|
2722
|
+
const memories = await memoryStore(
|
|
2723
|
+
context,
|
|
2724
|
+
runtimeContext
|
|
2725
|
+
).searchMemories({
|
|
2917
2726
|
query: parsedInput.query,
|
|
2918
2727
|
limit: boundedLimit2(parsedInput.limit, DEFAULT_SEARCH_LIMIT2)
|
|
2919
2728
|
});
|
|
@@ -2927,29 +2736,41 @@ function createMemorySearchTool(context) {
|
|
|
2927
2736
|
// src/process-session.ts
|
|
2928
2737
|
import { createHash as createHash2 } from "crypto";
|
|
2929
2738
|
import {
|
|
2930
|
-
getSourceKey as
|
|
2739
|
+
getSourceKey as getSourceKey3
|
|
2931
2740
|
} from "@sentry/junior-plugin-api";
|
|
2932
|
-
import { z as
|
|
2741
|
+
import { z as z8 } from "zod";
|
|
2933
2742
|
|
|
2934
2743
|
// src/events.ts
|
|
2935
2744
|
import { defineConversationEvent } from "@sentry/junior-plugin-api";
|
|
2936
|
-
import { z as
|
|
2937
|
-
var
|
|
2938
|
-
content:
|
|
2939
|
-
id:
|
|
2940
|
-
kind:
|
|
2941
|
-
observedAtMs:
|
|
2942
|
-
|
|
2745
|
+
import { z as z7 } from "zod";
|
|
2746
|
+
var capturedMemoryFields = {
|
|
2747
|
+
content: z7.string().min(1),
|
|
2748
|
+
id: z7.string().min(1),
|
|
2749
|
+
kind: z7.enum(MEMORY_KINDS),
|
|
2750
|
+
observedAtMs: z7.number().finite()
|
|
2751
|
+
};
|
|
2752
|
+
var legacyCapturedMemorySchema = z7.object({
|
|
2753
|
+
...capturedMemoryFields,
|
|
2754
|
+
scope: z7.enum(["personal", "conversation"])
|
|
2755
|
+
}).strict();
|
|
2756
|
+
var capturedMemorySchema = z7.object({
|
|
2757
|
+
...capturedMemoryFields,
|
|
2758
|
+
scope: z7.enum(MEMORY_SCOPES)
|
|
2943
2759
|
}).strict();
|
|
2944
|
-
var capturedMemoriesSchema =
|
|
2945
|
-
memories:
|
|
2946
|
-
costUsd:
|
|
2760
|
+
var capturedMemoriesSchema = z7.object({
|
|
2761
|
+
memories: z7.array(capturedMemorySchema).max(100),
|
|
2762
|
+
costUsd: z7.number().finite().nonnegative().optional()
|
|
2947
2763
|
}).strict();
|
|
2948
|
-
var recalledMemoriesSchema =
|
|
2764
|
+
var recalledMemoriesSchema = z7.object({
|
|
2949
2765
|
// Matches the automatic-recall candidate window; admission packs by char budget.
|
|
2950
|
-
memories:
|
|
2951
|
-
costUsd:
|
|
2766
|
+
memories: z7.array(z7.string().min(1)).max(20),
|
|
2767
|
+
costUsd: z7.number().finite().nonnegative().optional()
|
|
2952
2768
|
}).strict();
|
|
2769
|
+
function currentScope(scope) {
|
|
2770
|
+
if (scope === "personal") return "private";
|
|
2771
|
+
if (scope === "conversation") return "public";
|
|
2772
|
+
return scope;
|
|
2773
|
+
}
|
|
2953
2774
|
function renderCapturedMemories(event) {
|
|
2954
2775
|
const count = event.memories.length;
|
|
2955
2776
|
if (count === 0) return void 0;
|
|
@@ -2958,15 +2779,15 @@ function renderCapturedMemories(event) {
|
|
|
2958
2779
|
title: `${count} ${count === 1 ? "memory" : "memories"} captured`,
|
|
2959
2780
|
details: event.memories.map((memory) => ({
|
|
2960
2781
|
title: memory.content,
|
|
2961
|
-
metadata: [memory.kind, memory.scope]
|
|
2782
|
+
metadata: [memory.kind, currentScope(memory.scope)]
|
|
2962
2783
|
}))
|
|
2963
2784
|
};
|
|
2964
2785
|
}
|
|
2965
2786
|
var memoriesCapturedEventV1 = defineConversationEvent({
|
|
2966
2787
|
name: "memories_captured",
|
|
2967
2788
|
version: 1,
|
|
2968
|
-
schema:
|
|
2969
|
-
memories:
|
|
2789
|
+
schema: z7.object({
|
|
2790
|
+
memories: z7.array(legacyCapturedMemorySchema).min(1).max(100)
|
|
2970
2791
|
}).strict(),
|
|
2971
2792
|
renderEvent: renderCapturedMemories
|
|
2972
2793
|
});
|
|
@@ -3002,28 +2823,19 @@ var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
3002
2823
|
"searchMemories"
|
|
3003
2824
|
]);
|
|
3004
2825
|
var MEMORY_TASK_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3005
|
-
var extractedMemorySchema2 =
|
|
3006
|
-
content:
|
|
3007
|
-
expiresAtMs:
|
|
3008
|
-
kind:
|
|
3009
|
-
evidenceMessageIndices:
|
|
2826
|
+
var extractedMemorySchema2 = z8.object({
|
|
2827
|
+
content: z8.string().min(1),
|
|
2828
|
+
expiresAtMs: z8.number().finite().nullable(),
|
|
2829
|
+
kind: z8.enum(MEMORY_KINDS),
|
|
2830
|
+
evidenceMessageIndices: z8.array(z8.number().int().nonnegative()).min(1).max(10)
|
|
3010
2831
|
}).strict().transform(parseExtractedMemory);
|
|
3011
|
-
var extractedMemoryCacheSchema =
|
|
3012
|
-
|
|
3013
|
-
costUsd:
|
|
3014
|
-
memories:
|
|
2832
|
+
var extractedMemoryCacheSchema = z8.union([
|
|
2833
|
+
z8.object({
|
|
2834
|
+
costUsd: z8.number().finite().nonnegative().optional(),
|
|
2835
|
+
memories: z8.array(extractedMemorySchema2).max(5)
|
|
3015
2836
|
}).strict(),
|
|
3016
|
-
|
|
2837
|
+
z8.array(extractedMemorySchema2).max(5).transform((memories) => ({ memories }))
|
|
3017
2838
|
]);
|
|
3018
|
-
function allowsPassiveMemoryExtraction(source) {
|
|
3019
|
-
switch (source.platform) {
|
|
3020
|
-
case "local":
|
|
3021
|
-
return true;
|
|
3022
|
-
case "web":
|
|
3023
|
-
case "slack":
|
|
3024
|
-
return source.visibility === "public";
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
2839
|
function recordCapturedMemory(captured, result) {
|
|
3028
2840
|
const supersededIds = new Set(result.supersededIds ?? []);
|
|
3029
2841
|
for (let index2 = captured.length - 1; index2 >= 0; index2 -= 1) {
|
|
@@ -3069,11 +2881,11 @@ function routeExtractedMemory(memory, transcript, run) {
|
|
|
3069
2881
|
return "drop";
|
|
3070
2882
|
}
|
|
3071
2883
|
if (memory.kind === "preference") {
|
|
3072
|
-
const exactlyOneHumanRunActor = run.actor !== void 0 && run.actor.platform !== "system" && run.actors.length === 1 && run.actors[0]?.platform !== "system";
|
|
2884
|
+
const exactlyOneHumanRunActor = run.actorUserId !== void 0 && run.actor !== void 0 && run.actor.platform !== "system" && run.actors.length === 1 && run.actors[0]?.platform !== "system";
|
|
3073
2885
|
if (!exactlyOneHumanRunActor) {
|
|
3074
2886
|
return "drop";
|
|
3075
2887
|
}
|
|
3076
|
-
return cited.entries.every(isRunActorInstruction) ? "
|
|
2888
|
+
return cited.entries.every(isRunActorInstruction) ? "user" : "drop";
|
|
3077
2889
|
}
|
|
3078
2890
|
return cited.entries.every(
|
|
3079
2891
|
(entry) => isRunActorInstruction(entry) || isConversationEvidence(entry)
|
|
@@ -3106,16 +2918,16 @@ async function getTaskExtraction(context, extract) {
|
|
|
3106
2918
|
}
|
|
3107
2919
|
async function processMemorySession(context) {
|
|
3108
2920
|
const run = await context.run.load();
|
|
2921
|
+
if (run.source.platform === "local") {
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
3109
2924
|
if (run.transcript.some(
|
|
3110
2925
|
(entry) => entry.type === "toolResult" && MEMORY_TOOL_NAMES.has(entry.toolName)
|
|
3111
2926
|
)) {
|
|
3112
2927
|
return;
|
|
3113
2928
|
}
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
}
|
|
3117
|
-
const sourceKey2 = getSourceKey2(run.source);
|
|
3118
|
-
if (!sourceKey2) {
|
|
2929
|
+
const sourceKey2 = getSourceKey3(run.source);
|
|
2930
|
+
if (!sourceKey2 || run.source.visibility === "private" && !run.actorUserId) {
|
|
3119
2931
|
return;
|
|
3120
2932
|
}
|
|
3121
2933
|
const transcript = run.transcript.filter((entry) => entry.text?.trim()).map((entry) => ({ ...entry, text: entry.text.trim() }));
|
|
@@ -3125,8 +2937,10 @@ async function processMemorySession(context) {
|
|
|
3125
2937
|
}
|
|
3126
2938
|
const runtimeContext = memoryRuntimeContextSchema.parse({
|
|
3127
2939
|
conversationId: run.conversationId,
|
|
2940
|
+
...run.locationId ? { locationId: run.locationId } : void 0,
|
|
3128
2941
|
...run.actor ? { actor: run.actor } : void 0,
|
|
3129
|
-
source: run.source
|
|
2942
|
+
source: run.source,
|
|
2943
|
+
...run.actorUserId ? { userId: run.actorUserId } : void 0
|
|
3130
2944
|
});
|
|
3131
2945
|
const agent = createMemoryAgent(context.model);
|
|
3132
2946
|
const store = createMemoryStore(context.db, runtimeContext, {
|
|
@@ -3175,7 +2989,7 @@ async function processMemorySession(context) {
|
|
|
3175
2989
|
import {
|
|
3176
2990
|
definePromptContext
|
|
3177
2991
|
} from "@sentry/junior-plugin-api";
|
|
3178
|
-
import { z as
|
|
2992
|
+
import { z as z9 } from "zod";
|
|
3179
2993
|
var RECALL_CANDIDATE_LIMIT = 20;
|
|
3180
2994
|
var MAX_PROMPT_CHARS = 4e3;
|
|
3181
2995
|
var MAX_MEMORY_LINE_CHARS = 600;
|
|
@@ -3189,16 +3003,17 @@ function trimContent(content, maxLength) {
|
|
|
3189
3003
|
function formatObservedDate(observedAtMs) {
|
|
3190
3004
|
return new Date(observedAtMs).toISOString().slice(0, 10);
|
|
3191
3005
|
}
|
|
3192
|
-
var recalledMemorySchema =
|
|
3193
|
-
id:
|
|
3194
|
-
content:
|
|
3195
|
-
observedAtMs:
|
|
3196
|
-
scope
|
|
3197
|
-
|
|
3006
|
+
var recalledMemorySchema = z9.object({
|
|
3007
|
+
id: z9.string().min(1),
|
|
3008
|
+
content: z9.string().min(1).max(MAX_MEMORY_LINE_CHARS),
|
|
3009
|
+
observedAtMs: z9.number().finite(),
|
|
3010
|
+
// Stored version 1 uses the old scope labels. Prompt rendering ignores them.
|
|
3011
|
+
scope: z9.enum(["personal", "conversation"]),
|
|
3012
|
+
kind: z9.enum(["preference", "procedure", "knowledge"])
|
|
3198
3013
|
}).strict();
|
|
3199
|
-
var memoryRecallContextSchema =
|
|
3014
|
+
var memoryRecallContextSchema = z9.object({
|
|
3200
3015
|
// Count is a safety rail only. Admission packs by MAX_PROMPT_CHARS.
|
|
3201
|
-
memories:
|
|
3016
|
+
memories: z9.array(recalledMemorySchema).min(1).max(RECALL_CANDIDATE_LIMIT)
|
|
3202
3017
|
}).strict();
|
|
3203
3018
|
function selectPromptMemories(memories) {
|
|
3204
3019
|
const header = "Relevant memories for this request:";
|
|
@@ -3215,7 +3030,7 @@ function selectPromptMemories(memories) {
|
|
|
3215
3030
|
id: memory.id,
|
|
3216
3031
|
content,
|
|
3217
3032
|
observedAtMs: memory.observedAtMs,
|
|
3218
|
-
scope: memory.scope,
|
|
3033
|
+
scope: memory.scope === "private" ? "personal" : "conversation",
|
|
3219
3034
|
kind: memory.kind
|
|
3220
3035
|
});
|
|
3221
3036
|
totalChars += line.length + 1;
|
|
@@ -3255,10 +3070,13 @@ async function createMemoryPromptContributions(context) {
|
|
|
3255
3070
|
if (!context.text.trim()) {
|
|
3256
3071
|
return void 0;
|
|
3257
3072
|
}
|
|
3073
|
+
const actorUser = (await context.users.resolveActor())?.user;
|
|
3258
3074
|
const runtimeContext = memoryRuntimeContextSchema.parse({
|
|
3259
3075
|
...context.conversationId ? { conversationId: context.conversationId } : void 0,
|
|
3260
3076
|
...context.actor ? { actor: context.actor } : void 0,
|
|
3261
|
-
|
|
3077
|
+
...context.locationId ? { locationId: context.locationId } : void 0,
|
|
3078
|
+
source: context.source,
|
|
3079
|
+
...actorUser ? { userId: actorUser.id } : void 0
|
|
3262
3080
|
});
|
|
3263
3081
|
let embeddingCostUsd;
|
|
3264
3082
|
const sourceEmbedder = context.embedder;
|
|
@@ -3311,14 +3129,14 @@ async function createMemoryPromptContributions(context) {
|
|
|
3311
3129
|
}
|
|
3312
3130
|
|
|
3313
3131
|
// src/operational-report.ts
|
|
3314
|
-
import { and as and4, eq as eq5, gt as gt4, isNull as
|
|
3315
|
-
import { z as
|
|
3132
|
+
import { and as and4, eq as eq5, gt as gt4, isNull as isNull4, or as or4, sql as sql4 } from "drizzle-orm";
|
|
3133
|
+
import { z as z10 } from "zod";
|
|
3316
3134
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
3317
3135
|
var WINDOWS = [7, 30, 90];
|
|
3318
|
-
var memoryDaySchema =
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3136
|
+
var memoryDaySchema = z10.object({
|
|
3137
|
+
date: z10.string().date(),
|
|
3138
|
+
private: z10.number().int().nonnegative(),
|
|
3139
|
+
public: z10.number().int().nonnegative()
|
|
3322
3140
|
}).strict();
|
|
3323
3141
|
function queryRows(result) {
|
|
3324
3142
|
if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
|
|
@@ -3350,11 +3168,11 @@ async function aggregateMemoryDays(args) {
|
|
|
3350
3168
|
to_timestamp(${table.createdAtMs} / 1000.0) AT TIME ZONE 'UTC'
|
|
3351
3169
|
) AS day,
|
|
3352
3170
|
count(*) FILTER (
|
|
3353
|
-
WHERE ${table.scope} = '
|
|
3354
|
-
)::integer AS
|
|
3171
|
+
WHERE ${table.scope} = 'private'
|
|
3172
|
+
)::integer AS private,
|
|
3355
3173
|
count(*) FILTER (
|
|
3356
|
-
WHERE ${table.scope} = '
|
|
3357
|
-
)::integer AS
|
|
3174
|
+
WHERE ${table.scope} = 'public'
|
|
3175
|
+
)::integer AS public
|
|
3358
3176
|
FROM ${table}
|
|
3359
3177
|
WHERE ${table.createdAtMs} >= ${start.getTime()}
|
|
3360
3178
|
AND ${table.createdAtMs} < ${endExclusiveMs}
|
|
@@ -3365,13 +3183,13 @@ async function aggregateMemoryDays(args) {
|
|
|
3365
3183
|
)
|
|
3366
3184
|
SELECT
|
|
3367
3185
|
to_char(days.day, 'YYYY-MM-DD') AS date,
|
|
3368
|
-
coalesce(daily.
|
|
3369
|
-
coalesce(daily.
|
|
3186
|
+
coalesce(daily.private, 0)::integer AS private,
|
|
3187
|
+
coalesce(daily.public, 0)::integer AS public
|
|
3370
3188
|
FROM days
|
|
3371
3189
|
LEFT JOIN daily ON daily.day = days.day
|
|
3372
3190
|
ORDER BY days.day
|
|
3373
3191
|
`);
|
|
3374
|
-
return
|
|
3192
|
+
return z10.array(memoryDaySchema).parse(queryRows(result));
|
|
3375
3193
|
}
|
|
3376
3194
|
function formatCount(value) {
|
|
3377
3195
|
return new Intl.NumberFormat("en-US").format(value);
|
|
@@ -3393,18 +3211,18 @@ function formatUsd(value) {
|
|
|
3393
3211
|
}
|
|
3394
3212
|
async function buildMemoryOperationalReport(args) {
|
|
3395
3213
|
const active = and4(
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3214
|
+
isNull4(juniorMemoryMemories.archivedAtMs),
|
|
3215
|
+
isNull4(juniorMemoryMemories.supersededAtMs),
|
|
3216
|
+
isNull4(juniorMemoryMemories.supersededById),
|
|
3399
3217
|
or4(
|
|
3400
|
-
|
|
3218
|
+
isNull4(juniorMemoryMemories.expiresAtMs),
|
|
3401
3219
|
gt4(juniorMemoryMemories.expiresAtMs, args.nowMs)
|
|
3402
3220
|
)
|
|
3403
3221
|
);
|
|
3404
3222
|
const [[counts], memoryDays] = await Promise.all([
|
|
3405
3223
|
args.db.select({
|
|
3406
3224
|
active: sql4`count(*) filter (where ${active})`.mapWith(Number),
|
|
3407
|
-
|
|
3225
|
+
public: sql4`count(*) filter (where ${active} and ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
3408
3226
|
Number
|
|
3409
3227
|
),
|
|
3410
3228
|
createdThirtyDays: sql4`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${args.nowMs - 30 * DAY_MS2})`.mapWith(
|
|
@@ -3413,7 +3231,7 @@ async function buildMemoryOperationalReport(args) {
|
|
|
3413
3231
|
embedded: sql4`count(${juniorMemoryEmbeddings.memoryId}) filter (where ${active})`.mapWith(
|
|
3414
3232
|
Number
|
|
3415
3233
|
),
|
|
3416
|
-
|
|
3234
|
+
private: sql4`count(*) filter (where ${active} and ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
3417
3235
|
Number
|
|
3418
3236
|
)
|
|
3419
3237
|
}).from(juniorMemoryMemories).leftJoin(
|
|
@@ -3448,12 +3266,12 @@ async function buildMemoryOperationalReport(args) {
|
|
|
3448
3266
|
value: formatCount(counts?.createdThirtyDays ?? 0)
|
|
3449
3267
|
},
|
|
3450
3268
|
{
|
|
3451
|
-
label: "
|
|
3452
|
-
value: formatCount(counts?.
|
|
3269
|
+
label: "private",
|
|
3270
|
+
value: formatCount(counts?.private ?? 0)
|
|
3453
3271
|
},
|
|
3454
3272
|
{
|
|
3455
|
-
label: "
|
|
3456
|
-
value: formatCount(counts?.
|
|
3273
|
+
label: "public",
|
|
3274
|
+
value: formatCount(counts?.public ?? 0)
|
|
3457
3275
|
},
|
|
3458
3276
|
{
|
|
3459
3277
|
label: "embedding coverage",
|
|
@@ -3480,15 +3298,15 @@ async function buildMemoryOperationalReport(args) {
|
|
|
3480
3298
|
id: day.date,
|
|
3481
3299
|
label: day.date,
|
|
3482
3300
|
values: {
|
|
3483
|
-
|
|
3484
|
-
|
|
3301
|
+
private: day.private,
|
|
3302
|
+
public: day.public
|
|
3485
3303
|
}
|
|
3486
3304
|
})),
|
|
3487
3305
|
description: "Memories stored per day by scope",
|
|
3488
3306
|
id: "memories-created",
|
|
3489
3307
|
series: [
|
|
3490
|
-
{ key: "
|
|
3491
|
-
{ key: "
|
|
3308
|
+
{ key: "private", label: "Private" },
|
|
3309
|
+
{ key: "public", label: "Public" }
|
|
3492
3310
|
],
|
|
3493
3311
|
timeRangeDays: [...WINDOWS],
|
|
3494
3312
|
title: "Memories created",
|
|
@@ -3535,8 +3353,7 @@ function createMemoryUserPage() {
|
|
|
3535
3353
|
navigation: "primary",
|
|
3536
3354
|
description: "Personal and public memories Junior can use across conversations.",
|
|
3537
3355
|
async read(ctx, input) {
|
|
3538
|
-
const
|
|
3539
|
-
const page = await memories.list({
|
|
3356
|
+
const page = await listMemories(ctx.db, ctx.viewer.id, {
|
|
3540
3357
|
cursor: input.cursor,
|
|
3541
3358
|
...pageFilter(input.filter),
|
|
3542
3359
|
limit: input.limit,
|
|
@@ -3597,7 +3414,9 @@ function memoryToolContext(ctx) {
|
|
|
3597
3414
|
...ctx.actor ? { actor: ctx.actor } : void 0,
|
|
3598
3415
|
db: ctx.db,
|
|
3599
3416
|
...ctx.embedder ? { embedder: ctx.embedder } : void 0,
|
|
3417
|
+
...ctx.locationId ? { locationId: ctx.locationId } : void 0,
|
|
3600
3418
|
source: ctx.source,
|
|
3419
|
+
users: ctx.users,
|
|
3601
3420
|
...ctx.userText ? { userText: ctx.userText } : void 0
|
|
3602
3421
|
};
|
|
3603
3422
|
}
|
|
@@ -3684,9 +3503,11 @@ function memoryPlugin(options = {}) {
|
|
|
3684
3503
|
db: ctx.db,
|
|
3685
3504
|
embedder: ctx.embedder,
|
|
3686
3505
|
events: ctx.events,
|
|
3506
|
+
...ctx.locationId ? { locationId: ctx.locationId } : void 0,
|
|
3687
3507
|
log: ctx.log,
|
|
3688
3508
|
source: ctx.source,
|
|
3689
|
-
text: ctx.text
|
|
3509
|
+
text: ctx.text,
|
|
3510
|
+
users: ctx.users
|
|
3690
3511
|
});
|
|
3691
3512
|
}
|
|
3692
3513
|
} : void 0
|