@sentry/junior-memory 0.124.1 → 0.125.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 +11 -1
- package/dist/agent.d.ts +12 -2
- package/dist/api.d.ts +23 -7
- package/dist/events.d.ts +17 -1
- package/dist/index.js +282 -106
- package/dist/index.js.map +1 -1
- package/dist/operational-report.d.ts +2 -1
- package/dist/personal-store.d.ts +22 -11
- package/dist/personal.d.ts +5 -3
- package/dist/recall.d.ts +2 -1
- package/dist/store.d.ts +1 -0
- package/dist/user-pages.d.ts +2 -2
- package/package.json +2 -2
- package/src/agent.ts +26 -8
- package/src/api.ts +32 -9
- package/src/events.ts +53 -16
- package/src/operational-report.ts +37 -1
- package/src/personal-store.ts +108 -46
- package/src/personal.ts +14 -6
- package/src/plugin.ts +17 -2
- package/src/process-session.ts +39 -28
- package/src/recall.ts +52 -5
- package/src/store.ts +2 -0
- package/src/tools.ts +1 -0
- package/src/user-pages.ts +34 -23
package/src/personal-store.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQL operations over
|
|
2
|
+
* SQL operations over memories visible to one authenticated viewer.
|
|
3
3
|
*
|
|
4
4
|
* A viewer may resolve to several runtime actors. This store combines their
|
|
5
|
-
* personal scopes
|
|
5
|
+
* personal scopes with authorized public workspace conversation scopes.
|
|
6
6
|
*/
|
|
7
7
|
import { and, asc, desc, eq, gt, ilike, like, lt, or, sql } from "drizzle-orm";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { MEMORY_KINDS, type MemoryRuntimeContext } from "./types";
|
|
19
19
|
|
|
20
20
|
const nonEmptyStringSchema = z.string().min(1);
|
|
21
|
+
const memoryVisibilitySchema = z.enum(["private", "public"]);
|
|
21
22
|
const personalMemoryCursorSchema = z
|
|
22
23
|
.object({
|
|
23
24
|
createdAtMs: z.number().finite(),
|
|
@@ -31,6 +32,7 @@ const personalMemoryPageInputSchema = z
|
|
|
31
32
|
limit: z.number().int().min(1).max(50),
|
|
32
33
|
origin: z.enum(["automatic", "explicit"]).optional(),
|
|
33
34
|
query: z.string().max(200).optional(),
|
|
35
|
+
visibility: memoryVisibilitySchema.optional(),
|
|
34
36
|
})
|
|
35
37
|
.strict();
|
|
36
38
|
const personalMemoryTimelineInputSchema = z
|
|
@@ -46,15 +48,18 @@ export type PersonalMemoryPageInput = z.output<
|
|
|
46
48
|
typeof personalMemoryPageInputSchema
|
|
47
49
|
>;
|
|
48
50
|
|
|
51
|
+
export type MemoryVisibility = z.output<typeof memoryVisibilitySchema>;
|
|
52
|
+
|
|
49
53
|
export interface PersonalMemoryPage {
|
|
50
54
|
memories: PersonalMemoryRecord[];
|
|
51
55
|
nextCursor?: PersonalMemoryCursor;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
/** Safe provenance attached to one viewer-
|
|
58
|
+
/** Safe provenance attached to one viewer-visible memory. */
|
|
55
59
|
export type PersonalMemoryRecord = MemoryRecord & {
|
|
56
60
|
origin: "automatic" | "explicit" | "other";
|
|
57
61
|
sourcePlatform: "local" | "slack";
|
|
62
|
+
visibility: MemoryVisibility;
|
|
58
63
|
};
|
|
59
64
|
|
|
60
65
|
/** Viewer-scoped active memory totals used by the dashboard. */
|
|
@@ -65,16 +70,17 @@ export interface PersonalMemoryStats {
|
|
|
65
70
|
embedded: number;
|
|
66
71
|
explicit: number;
|
|
67
72
|
knowledge: number;
|
|
73
|
+
personal: number;
|
|
68
74
|
preference: number;
|
|
69
75
|
procedure: number;
|
|
76
|
+
public: number;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
/** Viewer-scoped memory creation totals for one UTC calendar day. */
|
|
73
80
|
export interface PersonalMemoryDay {
|
|
74
81
|
date: string;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
procedure: number;
|
|
82
|
+
personal: number;
|
|
83
|
+
public: number;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/** Expected failure when a viewer does not own the requested memory. */
|
|
@@ -85,17 +91,17 @@ export class PersonalMemoryNotFoundError extends Error {
|
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
/** Viewer-scoped
|
|
94
|
+
/** Viewer-scoped memory operations shared by dashboard and REST. */
|
|
89
95
|
export interface PersonalMemoryCollection {
|
|
90
96
|
/** Archive one exact personal memory owned by a linked actor. */
|
|
91
97
|
archive(id: string): Promise<MemoryRecord>;
|
|
92
|
-
/** Read one exact
|
|
93
|
-
get(id: string): Promise<
|
|
94
|
-
/** List one stable page across every
|
|
98
|
+
/** Read one exact memory visible to a linked actor. */
|
|
99
|
+
get(id: string): Promise<PersonalMemoryRecord>;
|
|
100
|
+
/** List one stable page across every authorized viewer scope. */
|
|
95
101
|
list(input: PersonalMemoryPageInput): Promise<PersonalMemoryPage>;
|
|
96
|
-
/** Summarize active memories across every
|
|
102
|
+
/** Summarize active memories across every authorized viewer scope. */
|
|
97
103
|
stats(): Promise<PersonalMemoryStats>;
|
|
98
|
-
/** Read memory creation history across every
|
|
104
|
+
/** Read memory creation history across every authorized viewer scope. */
|
|
99
105
|
timeline(input: { days: number }): Promise<PersonalMemoryDay[]>;
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -112,7 +118,30 @@ function personalScopes(
|
|
|
112
118
|
];
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
|
|
121
|
+
/** Public workspace conversation scopes authorized by the viewer's Slack teams. */
|
|
122
|
+
function publicWorkspaceScopes(
|
|
123
|
+
runtimeContexts: MemoryRuntimeContext[],
|
|
124
|
+
): ResolvedMemoryScope[] {
|
|
125
|
+
return [
|
|
126
|
+
...new Map(
|
|
127
|
+
runtimeContexts.flatMap((context) => {
|
|
128
|
+
if (context.source.platform !== "slack" || !context.actor) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
if (context.actor.platform !== "slack") {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
const scope: ResolvedMemoryScope = {
|
|
135
|
+
scope: "conversation",
|
|
136
|
+
scopeKey: `slack:${context.actor.teamId}`,
|
|
137
|
+
};
|
|
138
|
+
return [[`${scope.scope}:${scope.scopeKey}`, scope] as const];
|
|
139
|
+
}),
|
|
140
|
+
).values(),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function scopePredicate(scopes: ResolvedMemoryScope[]) {
|
|
116
145
|
if (scopes.length === 0) return undefined;
|
|
117
146
|
return or(
|
|
118
147
|
...scopes.map((scope) =>
|
|
@@ -148,30 +177,67 @@ function memoryOrigin(
|
|
|
148
177
|
return "other";
|
|
149
178
|
}
|
|
150
179
|
|
|
180
|
+
function memoryVisibility(
|
|
181
|
+
scope: MemoryRecord["scope"],
|
|
182
|
+
): PersonalMemoryRecord["visibility"] {
|
|
183
|
+
return scope === "personal" ? "private" : "public";
|
|
184
|
+
}
|
|
185
|
+
|
|
151
186
|
function personalMemoryRecord(
|
|
152
187
|
row: typeof juniorMemoryMemories.$inferSelect,
|
|
153
188
|
): PersonalMemoryRecord {
|
|
189
|
+
const memory = parseMemoryRow(row);
|
|
154
190
|
return {
|
|
155
|
-
...
|
|
191
|
+
...memory,
|
|
156
192
|
origin: memoryOrigin(row.idempotencyKey),
|
|
157
193
|
sourcePlatform: row.sourcePlatform,
|
|
194
|
+
visibility: memoryVisibility(memory.scope),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function emptyStats(): PersonalMemoryStats {
|
|
199
|
+
return {
|
|
200
|
+
active: 0,
|
|
201
|
+
automatic: 0,
|
|
202
|
+
createdThirtyDays: 0,
|
|
203
|
+
embedded: 0,
|
|
204
|
+
explicit: 0,
|
|
205
|
+
knowledge: 0,
|
|
206
|
+
personal: 0,
|
|
207
|
+
preference: 0,
|
|
208
|
+
procedure: 0,
|
|
209
|
+
public: 0,
|
|
158
210
|
};
|
|
159
211
|
}
|
|
160
212
|
|
|
161
|
-
/** Build storage operations for every
|
|
213
|
+
/** Build storage operations for every memory scope linked to one viewer. */
|
|
162
214
|
export function createPersonalMemoryCollection(
|
|
163
215
|
db: MemoryDb,
|
|
164
216
|
runtimeContexts: MemoryRuntimeContext[],
|
|
165
217
|
options: { now?: () => number } = {},
|
|
166
218
|
): PersonalMemoryCollection {
|
|
167
|
-
const
|
|
219
|
+
const privateScopes = personalScopes(runtimeContexts);
|
|
220
|
+
const publicScopes = publicWorkspaceScopes(runtimeContexts);
|
|
221
|
+
const allScopes = [...privateScopes, ...publicScopes];
|
|
168
222
|
const getNowMs = () => options.now?.() ?? Date.now();
|
|
169
223
|
|
|
224
|
+
function scopesForVisibility(
|
|
225
|
+
visibility: MemoryVisibility | undefined,
|
|
226
|
+
): ResolvedMemoryScope[] {
|
|
227
|
+
if (visibility === "private") return privateScopes;
|
|
228
|
+
if (visibility === "public") return publicScopes;
|
|
229
|
+
return allScopes;
|
|
230
|
+
}
|
|
231
|
+
|
|
170
232
|
return {
|
|
171
233
|
async archive(id) {
|
|
172
234
|
const memoryId = nonEmptyStringSchema.parse(id);
|
|
173
235
|
const nowMs = getNowMs();
|
|
174
|
-
|
|
236
|
+
// Forget is personal-only; public workspace memories stay shared.
|
|
237
|
+
const predicate = activeVisiblePredicate({
|
|
238
|
+
nowMs,
|
|
239
|
+
scopes: privateScopes,
|
|
240
|
+
});
|
|
175
241
|
if (!predicate) {
|
|
176
242
|
throw new PersonalMemoryNotFoundError();
|
|
177
243
|
}
|
|
@@ -195,7 +261,7 @@ export function createPersonalMemoryCollection(
|
|
|
195
261
|
async get(id) {
|
|
196
262
|
const memoryId = nonEmptyStringSchema.parse(id);
|
|
197
263
|
const nowMs = getNowMs();
|
|
198
|
-
const predicate = activeVisiblePredicate({ nowMs, scopes });
|
|
264
|
+
const predicate = activeVisiblePredicate({ nowMs, scopes: allScopes });
|
|
199
265
|
if (!predicate) {
|
|
200
266
|
throw new PersonalMemoryNotFoundError();
|
|
201
267
|
}
|
|
@@ -207,12 +273,13 @@ export function createPersonalMemoryCollection(
|
|
|
207
273
|
if (!rows[0]) {
|
|
208
274
|
throw new PersonalMemoryNotFoundError();
|
|
209
275
|
}
|
|
210
|
-
return
|
|
276
|
+
return personalMemoryRecord(rows[0]);
|
|
211
277
|
},
|
|
212
278
|
|
|
213
279
|
async list(input) {
|
|
214
280
|
input = personalMemoryPageInputSchema.parse(input);
|
|
215
281
|
const nowMs = getNowMs();
|
|
282
|
+
const scopes = scopesForVisibility(input.visibility);
|
|
216
283
|
await archiveExpiredMemoryBatch({ db, nowMs, scopes });
|
|
217
284
|
const active = activeVisiblePredicate({ nowMs, scopes });
|
|
218
285
|
if (!active) {
|
|
@@ -275,19 +342,10 @@ export function createPersonalMemoryCollection(
|
|
|
275
342
|
|
|
276
343
|
async stats() {
|
|
277
344
|
const nowMs = getNowMs();
|
|
278
|
-
await archiveExpiredMemoryBatch({ db, nowMs, scopes });
|
|
279
|
-
const active = activeVisiblePredicate({ nowMs, scopes });
|
|
345
|
+
await archiveExpiredMemoryBatch({ db, nowMs, scopes: allScopes });
|
|
346
|
+
const active = activeVisiblePredicate({ nowMs, scopes: allScopes });
|
|
280
347
|
if (!active) {
|
|
281
|
-
return
|
|
282
|
-
active: 0,
|
|
283
|
-
automatic: 0,
|
|
284
|
-
createdThirtyDays: 0,
|
|
285
|
-
embedded: 0,
|
|
286
|
-
explicit: 0,
|
|
287
|
-
knowledge: 0,
|
|
288
|
-
preference: 0,
|
|
289
|
-
procedure: 0,
|
|
290
|
-
};
|
|
348
|
+
return emptyStats();
|
|
291
349
|
}
|
|
292
350
|
const [counts] = await db
|
|
293
351
|
.select({
|
|
@@ -312,6 +370,10 @@ export function createPersonalMemoryCollection(
|
|
|
312
370
|
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'knowledge')`.mapWith(
|
|
313
371
|
Number,
|
|
314
372
|
),
|
|
373
|
+
personal:
|
|
374
|
+
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
|
|
375
|
+
Number,
|
|
376
|
+
),
|
|
315
377
|
preference:
|
|
316
378
|
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'preference')`.mapWith(
|
|
317
379
|
Number,
|
|
@@ -320,6 +382,10 @@ export function createPersonalMemoryCollection(
|
|
|
320
382
|
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
|
|
321
383
|
Number,
|
|
322
384
|
),
|
|
385
|
+
public:
|
|
386
|
+
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
|
|
387
|
+
Number,
|
|
388
|
+
),
|
|
323
389
|
})
|
|
324
390
|
.from(juniorMemoryMemories)
|
|
325
391
|
.leftJoin(
|
|
@@ -334,8 +400,10 @@ export function createPersonalMemoryCollection(
|
|
|
334
400
|
embedded: counts?.embedded ?? 0,
|
|
335
401
|
explicit: counts?.explicit ?? 0,
|
|
336
402
|
knowledge: counts?.knowledge ?? 0,
|
|
403
|
+
personal: counts?.personal ?? 0,
|
|
337
404
|
preference: counts?.preference ?? 0,
|
|
338
405
|
procedure: counts?.procedure ?? 0,
|
|
406
|
+
public: counts?.public ?? 0,
|
|
339
407
|
};
|
|
340
408
|
},
|
|
341
409
|
|
|
@@ -343,13 +411,12 @@ export function createPersonalMemoryCollection(
|
|
|
343
411
|
input = personalMemoryTimelineInputSchema.parse(input);
|
|
344
412
|
const todayMs = Date.parse(`${utcDate(getNowMs())}T00:00:00.000Z`);
|
|
345
413
|
const startMs = todayMs - (input.days - 1) * DAY_MS;
|
|
346
|
-
const ownership =
|
|
414
|
+
const ownership = scopePredicate(allScopes);
|
|
347
415
|
if (!ownership) {
|
|
348
416
|
return Array.from({ length: input.days }, (_, index) => ({
|
|
349
417
|
date: utcDate(startMs + index * DAY_MS),
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
procedure: 0,
|
|
418
|
+
personal: 0,
|
|
419
|
+
public: 0,
|
|
353
420
|
}));
|
|
354
421
|
}
|
|
355
422
|
const rows = await db
|
|
@@ -357,16 +424,12 @@ export function createPersonalMemoryCollection(
|
|
|
357
424
|
date: sql<string>`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`.as(
|
|
358
425
|
"date",
|
|
359
426
|
),
|
|
360
|
-
|
|
361
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.
|
|
427
|
+
personal:
|
|
428
|
+
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
|
|
362
429
|
Number,
|
|
363
430
|
),
|
|
364
|
-
|
|
365
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.
|
|
366
|
-
Number,
|
|
367
|
-
),
|
|
368
|
-
procedure:
|
|
369
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
|
|
431
|
+
public:
|
|
432
|
+
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
|
|
370
433
|
Number,
|
|
371
434
|
),
|
|
372
435
|
})
|
|
@@ -383,9 +446,8 @@ export function createPersonalMemoryCollection(
|
|
|
383
446
|
const row = byDate.get(date);
|
|
384
447
|
return {
|
|
385
448
|
date,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
procedure: row?.procedure ?? 0,
|
|
449
|
+
personal: row?.personal ?? 0,
|
|
450
|
+
public: row?.public ?? 0,
|
|
389
451
|
};
|
|
390
452
|
});
|
|
391
453
|
},
|
package/src/personal.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Authenticated-viewer memory access shared by REST and dashboard projections.
|
|
3
3
|
*
|
|
4
4
|
* Viewer identity may resolve to multiple platform actors. This module keeps
|
|
5
|
-
* that federation behind one
|
|
5
|
+
* that federation behind one viewer-memory collection spanning personal and
|
|
6
|
+
* authorized public workspace scopes.
|
|
6
7
|
*/
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import type { PluginUserPageActor } from "@sentry/junior-plugin-api";
|
|
9
10
|
import {
|
|
10
11
|
createPersonalMemoryCollection,
|
|
12
|
+
type MemoryVisibility,
|
|
11
13
|
type PersonalMemoryRecord,
|
|
12
14
|
} from "./personal-store";
|
|
13
15
|
import type { MemoryDb, MemoryRecord } from "./store";
|
|
@@ -21,6 +23,7 @@ const cursorSchema = z
|
|
|
21
23
|
origin: z.enum(["automatic", "explicit"]).optional(),
|
|
22
24
|
query: z.string().max(200).optional(),
|
|
23
25
|
version: z.literal(1),
|
|
26
|
+
visibility: z.enum(["private", "public"]).optional(),
|
|
24
27
|
})
|
|
25
28
|
.strict();
|
|
26
29
|
|
|
@@ -35,6 +38,7 @@ export interface ViewerMemoryPageInput {
|
|
|
35
38
|
limit: number;
|
|
36
39
|
origin?: "automatic" | "explicit";
|
|
37
40
|
query?: string;
|
|
41
|
+
visibility?: MemoryVisibility;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export class InvalidMemoryCursorError extends Error {
|
|
@@ -45,6 +49,7 @@ export class InvalidMemoryCursorError extends Error {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export { PersonalMemoryNotFoundError } from "./personal-store";
|
|
52
|
+
export type { MemoryVisibility, PersonalMemoryRecord } from "./personal-store";
|
|
48
53
|
|
|
49
54
|
function runtimeContext(actor: PluginUserPageActor): MemoryRuntimeContext {
|
|
50
55
|
if (actor.platform === "slack") {
|
|
@@ -70,7 +75,7 @@ function runtimeContext(actor: PluginUserPageActor): MemoryRuntimeContext {
|
|
|
70
75
|
|
|
71
76
|
function decodeCursor(
|
|
72
77
|
value: string | undefined,
|
|
73
|
-
input: Pick<ViewerMemoryPageInput, "kind" | "origin" | "query">,
|
|
78
|
+
input: Pick<ViewerMemoryPageInput, "kind" | "origin" | "query" | "visibility">,
|
|
74
79
|
) {
|
|
75
80
|
if (!value) return undefined;
|
|
76
81
|
try {
|
|
@@ -80,7 +85,8 @@ function decodeCursor(
|
|
|
80
85
|
if (
|
|
81
86
|
parsed.query !== input.query ||
|
|
82
87
|
parsed.kind !== input.kind ||
|
|
83
|
-
parsed.origin !== input.origin
|
|
88
|
+
parsed.origin !== input.origin ||
|
|
89
|
+
parsed.visibility !== input.visibility
|
|
84
90
|
) {
|
|
85
91
|
throw new InvalidMemoryCursorError();
|
|
86
92
|
}
|
|
@@ -92,7 +98,7 @@ function decodeCursor(
|
|
|
92
98
|
|
|
93
99
|
function encodeCursor(
|
|
94
100
|
cursor: { createdAtMs: number; id: string },
|
|
95
|
-
input: Pick<ViewerMemoryPageInput, "kind" | "origin" | "query">,
|
|
101
|
+
input: Pick<ViewerMemoryPageInput, "kind" | "origin" | "query" | "visibility">,
|
|
96
102
|
): string {
|
|
97
103
|
return Buffer.from(
|
|
98
104
|
JSON.stringify({
|
|
@@ -100,13 +106,14 @@ function encodeCursor(
|
|
|
100
106
|
...(input.query ? { query: input.query } : {}),
|
|
101
107
|
...(input.kind ? { kind: input.kind } : {}),
|
|
102
108
|
...(input.origin ? { origin: input.origin } : {}),
|
|
109
|
+
...(input.visibility ? { visibility: input.visibility } : {}),
|
|
103
110
|
version: 1,
|
|
104
111
|
}),
|
|
105
112
|
"utf8",
|
|
106
113
|
).toString("base64url");
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
/** Build
|
|
116
|
+
/** Build viewer memory operations authorized by a viewer's linked actors. */
|
|
110
117
|
export function createViewerMemories(
|
|
111
118
|
db: MemoryDb,
|
|
112
119
|
actors: PluginUserPageActor[],
|
|
@@ -119,7 +126,7 @@ export function createViewerMemories(
|
|
|
119
126
|
async archive(id: string): Promise<MemoryRecord> {
|
|
120
127
|
return await collection.archive(id);
|
|
121
128
|
},
|
|
122
|
-
async get(id: string): Promise<
|
|
129
|
+
async get(id: string): Promise<PersonalMemoryRecord> {
|
|
123
130
|
return await collection.get(id);
|
|
124
131
|
},
|
|
125
132
|
async list(input: ViewerMemoryPageInput): Promise<ViewerMemoryPage> {
|
|
@@ -128,6 +135,7 @@ export function createViewerMemories(
|
|
|
128
135
|
...(input.kind ? { kind: input.kind } : {}),
|
|
129
136
|
...(input.origin ? { origin: input.origin } : {}),
|
|
130
137
|
...(query ? { query } : {}),
|
|
138
|
+
...(input.visibility ? { visibility: input.visibility } : {}),
|
|
131
139
|
};
|
|
132
140
|
const page = await collection.list({
|
|
133
141
|
cursor: decodeCursor(input.cursor, filters),
|
package/src/plugin.ts
CHANGED
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
import { processMemorySession } from "./process-session";
|
|
15
15
|
import { createMemoryPromptContributions } from "./recall";
|
|
16
16
|
import { buildMemoryOperationalReport } from "./operational-report";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
memoriesCapturedEvent,
|
|
19
|
+
memoriesCapturedEventV1,
|
|
20
|
+
memoriesRecalledEvent,
|
|
21
|
+
} from "./events";
|
|
18
22
|
import type { MemoryDb } from "./store";
|
|
19
23
|
import { createMemoryUserPage } from "./user-pages";
|
|
20
24
|
|
|
@@ -101,7 +105,11 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
101
105
|
? { structuredModelId: modelId }
|
|
102
106
|
: { structuredModel: "default" },
|
|
103
107
|
packageName: "@sentry/junior-memory",
|
|
104
|
-
conversationEvents: [
|
|
108
|
+
conversationEvents: [
|
|
109
|
+
memoriesCapturedEventV1,
|
|
110
|
+
memoriesCapturedEvent,
|
|
111
|
+
memoriesRecalledEvent,
|
|
112
|
+
],
|
|
105
113
|
cli: {
|
|
106
114
|
commands: [createMemoryCliCommand()],
|
|
107
115
|
},
|
|
@@ -115,8 +123,13 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
115
123
|
userPages: [createMemoryUserPage()],
|
|
116
124
|
hooks: {
|
|
117
125
|
async operationalReport(ctx) {
|
|
126
|
+
const extractionDays = await ctx.eventStats.costsByDay({
|
|
127
|
+
days: 90,
|
|
128
|
+
eventName: "memories_captured",
|
|
129
|
+
});
|
|
118
130
|
return await buildMemoryOperationalReport({
|
|
119
131
|
db: ctx.db as MemoryDb,
|
|
132
|
+
extractionDays,
|
|
120
133
|
nowMs: ctx.nowMs,
|
|
121
134
|
});
|
|
122
135
|
},
|
|
@@ -124,6 +137,7 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
124
137
|
return createMemoryApi({
|
|
125
138
|
actors: ctx.viewer.actors,
|
|
126
139
|
db: ctx.db as MemoryDb,
|
|
140
|
+
eventStats: ctx.eventStats,
|
|
127
141
|
});
|
|
128
142
|
},
|
|
129
143
|
tools(ctx) {
|
|
@@ -156,6 +170,7 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
156
170
|
...(ctx.actor ? { actor: ctx.actor } : {}),
|
|
157
171
|
db: ctx.db as MemoryDb,
|
|
158
172
|
embedder: ctx.embedder,
|
|
173
|
+
events: ctx.events,
|
|
159
174
|
log: ctx.log,
|
|
160
175
|
maxVectorDistance: recallMaxVectorDistance(options),
|
|
161
176
|
source: ctx.source,
|
package/src/process-session.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createMemoryAgent,
|
|
18
18
|
parseExtractedMemory,
|
|
19
19
|
type ExtractedMemory,
|
|
20
|
+
type MemoryExtractionResult,
|
|
20
21
|
} from "./agent";
|
|
21
22
|
import { MEMORY_KINDS, memoryRuntimeContextSchema } from "./types";
|
|
22
23
|
import { capturedMemory, memoriesCapturedEvent } from "./events";
|
|
@@ -28,20 +29,30 @@ const MEMORY_TOOL_NAMES = new Set([
|
|
|
28
29
|
"searchMemories",
|
|
29
30
|
]);
|
|
30
31
|
const MEMORY_TASK_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
31
|
-
const
|
|
32
|
+
const extractedMemorySchema = z
|
|
33
|
+
.object({
|
|
34
|
+
content: z.string().min(1),
|
|
35
|
+
expiresAtMs: z.number().finite().nullable(),
|
|
36
|
+
kind: z.enum(MEMORY_KINDS),
|
|
37
|
+
evidenceMessageIndices: z
|
|
38
|
+
.array(z.number().int().nonnegative())
|
|
39
|
+
.min(1)
|
|
40
|
+
.max(10),
|
|
41
|
+
})
|
|
42
|
+
.strict()
|
|
43
|
+
.transform(parseExtractedMemory);
|
|
44
|
+
const extractedMemoryCacheSchema = z.union([
|
|
32
45
|
z
|
|
33
46
|
.object({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
kind: z.enum(MEMORY_KINDS),
|
|
37
|
-
evidenceMessageIndices: z
|
|
38
|
-
.array(z.number().int().nonnegative())
|
|
39
|
-
.min(1)
|
|
40
|
-
.max(10),
|
|
47
|
+
costUsd: z.number().finite().nonnegative().optional(),
|
|
48
|
+
memories: z.array(extractedMemorySchema).max(5),
|
|
41
49
|
})
|
|
42
|
-
.strict()
|
|
43
|
-
|
|
44
|
-
)
|
|
50
|
+
.strict(),
|
|
51
|
+
z
|
|
52
|
+
.array(extractedMemorySchema)
|
|
53
|
+
.max(5)
|
|
54
|
+
.transform((memories) => ({ memories })),
|
|
55
|
+
]);
|
|
45
56
|
|
|
46
57
|
/** Where a passively extracted memory may be stored, or dropped when unproven. */
|
|
47
58
|
type MemoryRouteTarget = "drop" | "personal" | "conversation";
|
|
@@ -178,10 +189,10 @@ function passiveInput(
|
|
|
178
189
|
};
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
async function
|
|
192
|
+
async function getTaskExtraction(
|
|
182
193
|
context: PluginTaskContext,
|
|
183
|
-
extract: () => Promise<
|
|
184
|
-
): Promise<
|
|
194
|
+
extract: () => Promise<MemoryExtractionResult>,
|
|
195
|
+
): Promise<MemoryExtractionResult> {
|
|
185
196
|
const cacheKey = `memory-extraction:${context.id}`;
|
|
186
197
|
const cached = await context.state.get(cacheKey);
|
|
187
198
|
if (cached !== undefined) {
|
|
@@ -191,11 +202,9 @@ async function getTaskMemories(
|
|
|
191
202
|
}
|
|
192
203
|
await context.state.delete(cacheKey);
|
|
193
204
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
return memories;
|
|
205
|
+
const extraction = await extract();
|
|
206
|
+
await context.state.set(cacheKey, extraction, MEMORY_TASK_STATE_TTL_MS);
|
|
207
|
+
return extraction;
|
|
199
208
|
}
|
|
200
209
|
|
|
201
210
|
/**
|
|
@@ -251,7 +260,7 @@ export async function processMemorySession(
|
|
|
251
260
|
supersessionDecider: agent,
|
|
252
261
|
});
|
|
253
262
|
await store.archiveExpiredMemories();
|
|
254
|
-
const
|
|
263
|
+
const extraction = await getTaskExtraction(context, async () => {
|
|
255
264
|
const existingMemories = await store.searchMemories({
|
|
256
265
|
limit: 10,
|
|
257
266
|
query: evidenceText,
|
|
@@ -265,14 +274,11 @@ export async function processMemorySession(
|
|
|
265
274
|
runtimeContext,
|
|
266
275
|
});
|
|
267
276
|
});
|
|
268
|
-
if (memories.length === 0) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
277
|
|
|
272
278
|
const captured: ReturnType<typeof capturedMemory>[] = [];
|
|
273
|
-
for (const memory of memories) {
|
|
279
|
+
for (const memory of extraction.memories) {
|
|
274
280
|
// The routing gate stays even though extraction is also actor-gated:
|
|
275
|
-
//
|
|
281
|
+
// getTaskExtraction caches extraction output for 7 days, so a retry can replay
|
|
276
282
|
// preference proposals cached before this gate existed.
|
|
277
283
|
const target = routeExtractedMemory(memory, transcript, run);
|
|
278
284
|
if (target === "drop") {
|
|
@@ -287,7 +293,12 @@ export async function processMemorySession(
|
|
|
287
293
|
const result = await store.createMemory(input);
|
|
288
294
|
recordCapturedMemory(captured, result);
|
|
289
295
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
await context.events.emit(
|
|
297
|
+
memoriesCapturedEvent({
|
|
298
|
+
memories: captured,
|
|
299
|
+
...(extraction.costUsd !== undefined
|
|
300
|
+
? { costUsd: extraction.costUsd }
|
|
301
|
+
: {}),
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
293
304
|
}
|