@sentry/junior-memory 0.120.0 → 0.122.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +163 -3
- package/dist/index.js.map +1 -1
- package/dist/operational-report.d.ts +7 -0
- package/dist/recall.d.ts +1 -6
- package/dist/tools.d.ts +126 -0
- package/package.json +2 -2
- package/src/operational-report.ts +191 -0
- package/src/plugin.ts +7 -0
- package/src/recall.ts +1 -9
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PluginOperationalReportContent } from "@sentry/junior-plugin-api";
|
|
2
|
+
import type { MemoryDb } from "./store";
|
|
3
|
+
/** Build aggregate memory storage and indexing diagnostics for the System page. */
|
|
4
|
+
export declare function buildMemoryOperationalReport(args: {
|
|
5
|
+
db: MemoryDb;
|
|
6
|
+
nowMs: number;
|
|
7
|
+
}): Promise<PluginOperationalReportContent>;
|
package/dist/recall.d.ts
CHANGED
|
@@ -31,10 +31,5 @@ export declare const memoryRecallContextSchema: z.ZodObject<{
|
|
|
31
31
|
}>;
|
|
32
32
|
}, z.core.$strict>>;
|
|
33
33
|
}, z.core.$strict>;
|
|
34
|
-
/**
|
|
35
|
-
* Build active memory recall contributions.
|
|
36
|
-
*
|
|
37
|
-
* Personal recall remains prompt-only so conversation reporting cannot widen
|
|
38
|
-
* actor-scoped memory visibility.
|
|
39
|
-
*/
|
|
34
|
+
/** Build active memory recall contributions. */
|
|
40
35
|
export declare function createMemoryPromptContributions(context: MemoryRecallContext): Promise<UserPromptContribution[] | undefined>;
|
package/dist/tools.d.ts
CHANGED
|
@@ -52,6 +52,39 @@ export declare function createMemoryCreateTool(context: MemoryCreateToolContext)
|
|
|
52
52
|
message: string;
|
|
53
53
|
retryable?: boolean | undefined;
|
|
54
54
|
} | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
[x: string]: unknown;
|
|
57
|
+
ok: true;
|
|
58
|
+
status: "success";
|
|
59
|
+
target: string;
|
|
60
|
+
data: {
|
|
61
|
+
created: boolean;
|
|
62
|
+
memory: {
|
|
63
|
+
id: string;
|
|
64
|
+
content: string;
|
|
65
|
+
createdAtMs: number;
|
|
66
|
+
observedAtMs: number;
|
|
67
|
+
expiresAtMs?: number | undefined;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
created: boolean;
|
|
71
|
+
memory: {
|
|
72
|
+
id: string;
|
|
73
|
+
content: string;
|
|
74
|
+
createdAtMs: number;
|
|
75
|
+
observedAtMs: number;
|
|
76
|
+
expiresAtMs?: number | undefined;
|
|
77
|
+
};
|
|
78
|
+
truncated?: boolean | undefined;
|
|
79
|
+
continuation?: {
|
|
80
|
+
arguments: Record<string, unknown>;
|
|
81
|
+
reason?: string | undefined;
|
|
82
|
+
} | undefined;
|
|
83
|
+
error?: string | {
|
|
84
|
+
kind: string;
|
|
85
|
+
message: string;
|
|
86
|
+
retryable?: boolean | undefined;
|
|
87
|
+
} | undefined;
|
|
55
88
|
}>;
|
|
56
89
|
/** Create a tool that archives a visible memory in the active context. */
|
|
57
90
|
export declare function createMemoryRemoveTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
@@ -87,6 +120,37 @@ export declare function createMemoryRemoveTool(context: MemoryToolContext): impo
|
|
|
87
120
|
message: string;
|
|
88
121
|
retryable?: boolean | undefined;
|
|
89
122
|
} | undefined;
|
|
123
|
+
}, {
|
|
124
|
+
[x: string]: unknown;
|
|
125
|
+
ok: true;
|
|
126
|
+
status: "success";
|
|
127
|
+
target: string;
|
|
128
|
+
data: {
|
|
129
|
+
memory: {
|
|
130
|
+
id: string;
|
|
131
|
+
content: string;
|
|
132
|
+
createdAtMs: number;
|
|
133
|
+
observedAtMs: number;
|
|
134
|
+
expiresAtMs?: number | undefined;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
memory: {
|
|
138
|
+
id: string;
|
|
139
|
+
content: string;
|
|
140
|
+
createdAtMs: number;
|
|
141
|
+
observedAtMs: number;
|
|
142
|
+
expiresAtMs?: number | undefined;
|
|
143
|
+
};
|
|
144
|
+
truncated?: boolean | undefined;
|
|
145
|
+
continuation?: {
|
|
146
|
+
arguments: Record<string, unknown>;
|
|
147
|
+
reason?: string | undefined;
|
|
148
|
+
} | undefined;
|
|
149
|
+
error?: string | {
|
|
150
|
+
kind: string;
|
|
151
|
+
message: string;
|
|
152
|
+
retryable?: boolean | undefined;
|
|
153
|
+
} | undefined;
|
|
90
154
|
}>;
|
|
91
155
|
/** Create a tool that lists visible active memories in the active context. */
|
|
92
156
|
export declare function createMemoryListTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
@@ -122,6 +186,37 @@ export declare function createMemoryListTool(context: MemoryToolContext): import
|
|
|
122
186
|
message: string;
|
|
123
187
|
retryable?: boolean | undefined;
|
|
124
188
|
} | undefined;
|
|
189
|
+
}, {
|
|
190
|
+
[x: string]: unknown;
|
|
191
|
+
ok: true;
|
|
192
|
+
status: "success";
|
|
193
|
+
target: string;
|
|
194
|
+
data: {
|
|
195
|
+
memories: {
|
|
196
|
+
id: string;
|
|
197
|
+
content: string;
|
|
198
|
+
createdAtMs: number;
|
|
199
|
+
observedAtMs: number;
|
|
200
|
+
expiresAtMs?: number | undefined;
|
|
201
|
+
}[];
|
|
202
|
+
};
|
|
203
|
+
memories: {
|
|
204
|
+
id: string;
|
|
205
|
+
content: string;
|
|
206
|
+
createdAtMs: number;
|
|
207
|
+
observedAtMs: number;
|
|
208
|
+
expiresAtMs?: number | undefined;
|
|
209
|
+
}[];
|
|
210
|
+
truncated?: boolean | undefined;
|
|
211
|
+
continuation?: {
|
|
212
|
+
arguments: Record<string, unknown>;
|
|
213
|
+
reason?: string | undefined;
|
|
214
|
+
} | undefined;
|
|
215
|
+
error?: string | {
|
|
216
|
+
kind: string;
|
|
217
|
+
message: string;
|
|
218
|
+
retryable?: boolean | undefined;
|
|
219
|
+
} | undefined;
|
|
125
220
|
}>;
|
|
126
221
|
/** Create a tool that searches visible active memories in the active context. */
|
|
127
222
|
export declare function createMemorySearchTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
@@ -158,4 +253,35 @@ export declare function createMemorySearchTool(context: MemoryToolContext): impo
|
|
|
158
253
|
message: string;
|
|
159
254
|
retryable?: boolean | undefined;
|
|
160
255
|
} | undefined;
|
|
256
|
+
}, {
|
|
257
|
+
[x: string]: unknown;
|
|
258
|
+
ok: true;
|
|
259
|
+
status: "success";
|
|
260
|
+
target: string;
|
|
261
|
+
data: {
|
|
262
|
+
memories: {
|
|
263
|
+
id: string;
|
|
264
|
+
content: string;
|
|
265
|
+
createdAtMs: number;
|
|
266
|
+
observedAtMs: number;
|
|
267
|
+
expiresAtMs?: number | undefined;
|
|
268
|
+
}[];
|
|
269
|
+
};
|
|
270
|
+
memories: {
|
|
271
|
+
id: string;
|
|
272
|
+
content: string;
|
|
273
|
+
createdAtMs: number;
|
|
274
|
+
observedAtMs: number;
|
|
275
|
+
expiresAtMs?: number | undefined;
|
|
276
|
+
}[];
|
|
277
|
+
truncated?: boolean | undefined;
|
|
278
|
+
continuation?: {
|
|
279
|
+
arguments: Record<string, unknown>;
|
|
280
|
+
reason?: string | undefined;
|
|
281
|
+
} | undefined;
|
|
282
|
+
error?: string | {
|
|
283
|
+
kind: string;
|
|
284
|
+
message: string;
|
|
285
|
+
retryable?: boolean | undefined;
|
|
286
|
+
} | undefined;
|
|
161
287
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.122.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"commander": "^14.0.3",
|
|
28
28
|
"drizzle-orm": "^0.45.2",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@sentry/junior-plugin-api": "0.
|
|
30
|
+
"@sentry/junior-plugin-api": "0.122.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.1",
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { PluginOperationalReportContent } from "@sentry/junior-plugin-api";
|
|
2
|
+
import { and, eq, gt, isNull, or, sql } from "drizzle-orm";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema";
|
|
5
|
+
import type { MemoryDb } from "./store";
|
|
6
|
+
|
|
7
|
+
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
8
|
+
const WINDOWS = [7, 30, 90] as const;
|
|
9
|
+
|
|
10
|
+
const memoryDaySchema = z
|
|
11
|
+
.object({
|
|
12
|
+
conversation: z.number().int().nonnegative(),
|
|
13
|
+
date: z.string().date(),
|
|
14
|
+
personal: z.number().int().nonnegative(),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
|
|
18
|
+
function queryRows(result: unknown): unknown[] {
|
|
19
|
+
if (
|
|
20
|
+
typeof result !== "object" ||
|
|
21
|
+
result === null ||
|
|
22
|
+
!("rows" in result) ||
|
|
23
|
+
!Array.isArray(result.rows)
|
|
24
|
+
) {
|
|
25
|
+
throw new TypeError("Memory activity query did not return rows");
|
|
26
|
+
}
|
|
27
|
+
return result.rows;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function startOfUtcDay(value: number): Date {
|
|
31
|
+
const date = new Date(value);
|
|
32
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
33
|
+
return date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function aggregateMemoryDays(args: { db: MemoryDb; nowMs: number }) {
|
|
37
|
+
const end = startOfUtcDay(args.nowMs);
|
|
38
|
+
const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1)! - 1) * DAY_MS);
|
|
39
|
+
const endExclusiveMs = end.getTime() + DAY_MS;
|
|
40
|
+
const table = juniorMemoryMemories;
|
|
41
|
+
const result = await args.db.execute(sql`
|
|
42
|
+
WITH days AS (
|
|
43
|
+
SELECT generate_series(
|
|
44
|
+
date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
|
|
45
|
+
date_trunc('day', ${end}::timestamptz AT TIME ZONE 'UTC'),
|
|
46
|
+
interval '1 day'
|
|
47
|
+
) AS day
|
|
48
|
+
), daily AS (
|
|
49
|
+
SELECT
|
|
50
|
+
date_trunc(
|
|
51
|
+
'day',
|
|
52
|
+
to_timestamp(${table.createdAtMs} / 1000.0) AT TIME ZONE 'UTC'
|
|
53
|
+
) AS day,
|
|
54
|
+
count(*) FILTER (
|
|
55
|
+
WHERE ${table.scope} = 'personal'
|
|
56
|
+
)::integer AS personal,
|
|
57
|
+
count(*) FILTER (
|
|
58
|
+
WHERE ${table.scope} = 'conversation'
|
|
59
|
+
)::integer AS conversation
|
|
60
|
+
FROM ${table}
|
|
61
|
+
WHERE ${table.createdAtMs} >= ${start.getTime()}
|
|
62
|
+
AND ${table.createdAtMs} < ${endExclusiveMs}
|
|
63
|
+
GROUP BY date_trunc(
|
|
64
|
+
'day',
|
|
65
|
+
to_timestamp(${table.createdAtMs} / 1000.0) AT TIME ZONE 'UTC'
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
SELECT
|
|
69
|
+
to_char(days.day, 'YYYY-MM-DD') AS date,
|
|
70
|
+
coalesce(daily.personal, 0)::integer AS personal,
|
|
71
|
+
coalesce(daily.conversation, 0)::integer AS conversation
|
|
72
|
+
FROM days
|
|
73
|
+
LEFT JOIN daily ON daily.day = days.day
|
|
74
|
+
ORDER BY days.day
|
|
75
|
+
`);
|
|
76
|
+
return z.array(memoryDaySchema).parse(queryRows(result));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatCount(value: number): string {
|
|
80
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatPercent(value: number): string {
|
|
84
|
+
return new Intl.NumberFormat("en-US", {
|
|
85
|
+
maximumFractionDigits: 0,
|
|
86
|
+
style: "percent",
|
|
87
|
+
}).format(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Build aggregate memory storage and indexing diagnostics for the System page. */
|
|
91
|
+
export async function buildMemoryOperationalReport(args: {
|
|
92
|
+
db: MemoryDb;
|
|
93
|
+
nowMs: number;
|
|
94
|
+
}): Promise<PluginOperationalReportContent> {
|
|
95
|
+
const active = and(
|
|
96
|
+
isNull(juniorMemoryMemories.archivedAtMs),
|
|
97
|
+
isNull(juniorMemoryMemories.supersededAtMs),
|
|
98
|
+
isNull(juniorMemoryMemories.supersededById),
|
|
99
|
+
or(
|
|
100
|
+
isNull(juniorMemoryMemories.expiresAtMs),
|
|
101
|
+
gt(juniorMemoryMemories.expiresAtMs, args.nowMs),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
const [[counts], memoryDays] = await Promise.all([
|
|
105
|
+
args.db
|
|
106
|
+
.select({
|
|
107
|
+
active: sql<number>`count(*) filter (where ${active})`.mapWith(Number),
|
|
108
|
+
conversation:
|
|
109
|
+
sql<number>`count(*) filter (where ${active} and ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
|
|
110
|
+
Number,
|
|
111
|
+
),
|
|
112
|
+
createdThirtyDays:
|
|
113
|
+
sql<number>`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${args.nowMs - 30 * DAY_MS})`.mapWith(
|
|
114
|
+
Number,
|
|
115
|
+
),
|
|
116
|
+
embedded:
|
|
117
|
+
sql<number>`count(${juniorMemoryEmbeddings.memoryId}) filter (where ${active})`.mapWith(
|
|
118
|
+
Number,
|
|
119
|
+
),
|
|
120
|
+
personal:
|
|
121
|
+
sql<number>`count(*) filter (where ${active} and ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
|
|
122
|
+
Number,
|
|
123
|
+
),
|
|
124
|
+
})
|
|
125
|
+
.from(juniorMemoryMemories)
|
|
126
|
+
.leftJoin(
|
|
127
|
+
juniorMemoryEmbeddings,
|
|
128
|
+
eq(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id),
|
|
129
|
+
),
|
|
130
|
+
aggregateMemoryDays(args),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const activeCount = counts?.active ?? 0;
|
|
134
|
+
const embeddedCount = counts?.embedded ?? 0;
|
|
135
|
+
const embeddingCoverage = activeCount === 0 ? 0 : embeddedCount / activeCount;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
generatedAt: new Date(args.nowMs).toISOString(),
|
|
139
|
+
title: "Memory",
|
|
140
|
+
metrics: [
|
|
141
|
+
{
|
|
142
|
+
label: "active memories",
|
|
143
|
+
tone: activeCount > 0 ? "good" : "neutral",
|
|
144
|
+
value: formatCount(activeCount),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
label: "created · 30d",
|
|
148
|
+
value: formatCount(counts?.createdThirtyDays ?? 0),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
label: "personal",
|
|
152
|
+
value: formatCount(counts?.personal ?? 0),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
label: "conversation",
|
|
156
|
+
value: formatCount(counts?.conversation ?? 0),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: "embedding coverage",
|
|
160
|
+
tone:
|
|
161
|
+
activeCount === 0
|
|
162
|
+
? "neutral"
|
|
163
|
+
: embeddedCount === activeCount
|
|
164
|
+
? "good"
|
|
165
|
+
: "warning",
|
|
166
|
+
value: formatPercent(embeddingCoverage),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
widgets: [
|
|
170
|
+
{
|
|
171
|
+
categories: memoryDays.map((day) => ({
|
|
172
|
+
id: day.date,
|
|
173
|
+
label: day.date,
|
|
174
|
+
values: {
|
|
175
|
+
conversation: day.conversation,
|
|
176
|
+
personal: day.personal,
|
|
177
|
+
},
|
|
178
|
+
})),
|
|
179
|
+
description: "Memories stored per day by scope",
|
|
180
|
+
id: "memories-created",
|
|
181
|
+
series: [
|
|
182
|
+
{ key: "personal", label: "Personal" },
|
|
183
|
+
{ key: "conversation", label: "Conversation" },
|
|
184
|
+
],
|
|
185
|
+
timeRangeDays: [...WINDOWS],
|
|
186
|
+
title: "Memories created",
|
|
187
|
+
type: "bar_chart",
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./tools";
|
|
14
14
|
import { processMemorySession } from "./process-session";
|
|
15
15
|
import { createMemoryPromptContributions } from "./recall";
|
|
16
|
+
import { buildMemoryOperationalReport } from "./operational-report";
|
|
16
17
|
import type { MemoryDb } from "./store";
|
|
17
18
|
import { createMemoryUserPage } from "./user-pages";
|
|
18
19
|
|
|
@@ -111,6 +112,12 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
111
112
|
},
|
|
112
113
|
userPages: [createMemoryUserPage()],
|
|
113
114
|
hooks: {
|
|
115
|
+
async operationalReport(ctx) {
|
|
116
|
+
return await buildMemoryOperationalReport({
|
|
117
|
+
db: ctx.db as MemoryDb,
|
|
118
|
+
nowMs: ctx.nowMs,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
114
121
|
apiRoutes(ctx) {
|
|
115
122
|
return createMemoryApi({
|
|
116
123
|
actors: ctx.viewer.actors,
|
package/src/recall.ts
CHANGED
|
@@ -108,12 +108,7 @@ const memoryRecallContext = definePromptContext({
|
|
|
108
108
|
renderPrompt: (content) => renderMemoryPrompt(content.memories),
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
/**
|
|
112
|
-
* Build active memory recall contributions.
|
|
113
|
-
*
|
|
114
|
-
* Personal recall remains prompt-only so conversation reporting cannot widen
|
|
115
|
-
* actor-scoped memory visibility.
|
|
116
|
-
*/
|
|
111
|
+
/** Build active memory recall contributions. */
|
|
117
112
|
export async function createMemoryPromptContributions(
|
|
118
113
|
context: MemoryRecallContext,
|
|
119
114
|
): Promise<UserPromptContribution[] | undefined> {
|
|
@@ -162,8 +157,5 @@ export async function createMemoryPromptContributions(
|
|
|
162
157
|
if (selected.length === 0) {
|
|
163
158
|
return undefined;
|
|
164
159
|
}
|
|
165
|
-
if (selected.some((memory) => memory.scope === "personal")) {
|
|
166
|
-
return [{ text: renderMemoryPrompt(selected) }];
|
|
167
|
-
}
|
|
168
160
|
return [memoryRecallContext({ memories: selected })];
|
|
169
161
|
}
|