@sentry/junior-memory 0.197.0 → 0.199.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.d.ts +887 -7
- package/package.json +5 -6
- package/dist/agent.d.ts +0 -280
- package/dist/api.d.ts +0 -121
- package/dist/cli/format.d.ts +0 -5
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/search.d.ts +0 -4
- package/dist/cli/show.d.ts +0 -4
- package/dist/db/schema.d.ts +0 -479
- package/dist/events.d.ts +0 -35
- package/dist/operational-report.d.ts +0 -8
- package/dist/plugin.d.ts +0 -9
- package/dist/process-session.d.ts +0 -9
- package/dist/ranking.d.ts +0 -18
- package/dist/recall.d.ts +0 -41
- package/dist/scope.d.ts +0 -19
- package/dist/store.d.ts +0 -229
- package/dist/tools.d.ts +0 -164
- package/dist/types.d.ts +0 -78
- package/dist/user-pages.d.ts +0 -4
- package/dist/viewer.d.ts +0 -91
- package/src/agent.ts +0 -663
- package/src/api.ts +0 -288
- package/src/cli/format.ts +0 -30
- package/src/cli/index.ts +0 -15
- package/src/cli/search.ts +0 -119
- package/src/cli/show.ts +0 -44
- package/src/db/schema.ts +0 -147
- package/src/events.ts +0 -107
- package/src/index.ts +0 -25
- package/src/operational-report.ts +0 -228
- package/src/plugin.ts +0 -186
- package/src/process-session.ts +0 -311
- package/src/ranking.ts +0 -107
- package/src/recall.ts +0 -212
- package/src/scope.ts +0 -75
- package/src/store.ts +0 -1578
- package/src/tools.ts +0 -586
- package/src/types.ts +0 -37
- package/src/user-pages.ts +0 -99
- package/src/viewer.ts +0 -441
package/src/user-pages.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/** Render memory in Junior's User page format. */
|
|
2
|
-
import type { PluginUserPageDefinition } from "@sentry/junior-plugin-api";
|
|
3
|
-
import { listMemories, type MemoryVisibility, type MemoryView } from "./viewer";
|
|
4
|
-
import type { MemoryDb } from "./store";
|
|
5
|
-
|
|
6
|
-
function titleCase(value: string): string {
|
|
7
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function rememberedDate(createdAtMs: number): string {
|
|
11
|
-
return new Intl.DateTimeFormat("en-US", {
|
|
12
|
-
dateStyle: "medium",
|
|
13
|
-
timeStyle: "short",
|
|
14
|
-
timeZone: "UTC",
|
|
15
|
-
}).format(new Date(createdAtMs));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function originLabel(origin: MemoryView["origin"]): string {
|
|
19
|
-
if (origin === "automatic") return "Automatic";
|
|
20
|
-
if (origin === "explicit") return "Explicit";
|
|
21
|
-
return "Other";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function visibilityLabel(visibility: MemoryVisibility): string {
|
|
25
|
-
return visibility === "public" ? "Public" : "Private";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function pageFilter(filter: string | undefined): {
|
|
29
|
-
visibility?: MemoryVisibility;
|
|
30
|
-
} {
|
|
31
|
-
if (filter === "private") return { visibility: "private" };
|
|
32
|
-
if (filter === "public") return { visibility: "public" };
|
|
33
|
-
return {};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function pageEmptyText(input: { filter?: string; query?: string }): string {
|
|
37
|
-
if (input.query) return "No memories matched your search.";
|
|
38
|
-
if (input.filter === "private") return "No private memories yet.";
|
|
39
|
-
if (input.filter === "public") return "No public memories yet.";
|
|
40
|
-
return "No memories yet.";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Create the interactive Memories dashboard page. */
|
|
44
|
-
export function createMemoryUserPage(): PluginUserPageDefinition {
|
|
45
|
-
return {
|
|
46
|
-
id: "memories",
|
|
47
|
-
label: "Memories",
|
|
48
|
-
navigation: "primary",
|
|
49
|
-
description:
|
|
50
|
-
"Personal and public memories Junior can use across conversations.",
|
|
51
|
-
async read(ctx, input) {
|
|
52
|
-
const page = await listMemories(ctx.db as MemoryDb, ctx.viewer.id, {
|
|
53
|
-
cursor: input.cursor,
|
|
54
|
-
...pageFilter(input.filter),
|
|
55
|
-
limit: input.limit,
|
|
56
|
-
...(input.query ? { query: input.query } : undefined),
|
|
57
|
-
});
|
|
58
|
-
return {
|
|
59
|
-
type: "list",
|
|
60
|
-
emptyText: pageEmptyText(input),
|
|
61
|
-
...(page.nextCursor ? { nextCursor: page.nextCursor } : undefined),
|
|
62
|
-
searchPlaceholder: "Search memories",
|
|
63
|
-
records: page.memories.map((memory) => ({
|
|
64
|
-
actions:
|
|
65
|
-
memory.visibility === "private"
|
|
66
|
-
? [
|
|
67
|
-
{
|
|
68
|
-
confirmation: "Forget this memory?",
|
|
69
|
-
href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`,
|
|
70
|
-
label: "Forget",
|
|
71
|
-
method: "DELETE" as const,
|
|
72
|
-
tone: "danger" as const,
|
|
73
|
-
},
|
|
74
|
-
]
|
|
75
|
-
: [],
|
|
76
|
-
id: memory.id,
|
|
77
|
-
title: memory.content,
|
|
78
|
-
metadata: [
|
|
79
|
-
{ label: "Type", value: titleCase(memory.kind) },
|
|
80
|
-
{ label: "Learned", value: originLabel(memory.origin) },
|
|
81
|
-
{ label: "Source", value: titleCase(memory.sourcePlatform) },
|
|
82
|
-
{
|
|
83
|
-
label: "Visibility",
|
|
84
|
-
value: visibilityLabel(memory.visibility),
|
|
85
|
-
},
|
|
86
|
-
{ label: "Remembered", value: rememberedDate(memory.createdAtMs) },
|
|
87
|
-
{ label: "Observed", value: rememberedDate(memory.observedAtMs) },
|
|
88
|
-
{
|
|
89
|
-
label: "Expires",
|
|
90
|
-
value: memory.expiresAtMs
|
|
91
|
-
? rememberedDate(memory.expiresAtMs)
|
|
92
|
-
: "Never",
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
})),
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
package/src/viewer.ts
DELETED
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory access for an authenticated User.
|
|
3
|
-
*
|
|
4
|
-
* Every User can read public memory and private memory that they own.
|
|
5
|
-
*/
|
|
6
|
-
import {
|
|
7
|
-
and,
|
|
8
|
-
asc,
|
|
9
|
-
desc,
|
|
10
|
-
eq,
|
|
11
|
-
gt,
|
|
12
|
-
ilike,
|
|
13
|
-
isNull,
|
|
14
|
-
like,
|
|
15
|
-
lt,
|
|
16
|
-
or,
|
|
17
|
-
sql,
|
|
18
|
-
} from "drizzle-orm";
|
|
19
|
-
import { z } from "zod";
|
|
20
|
-
import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema";
|
|
21
|
-
import { publicMemoryScope } from "./scope";
|
|
22
|
-
import { parseMemoryRow, type MemoryDb, type MemoryRecord } from "./store";
|
|
23
|
-
import { MEMORY_KINDS, type MemorySourcePlatform } from "./types";
|
|
24
|
-
|
|
25
|
-
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
26
|
-
const nonEmptyStringSchema = z.string().min(1);
|
|
27
|
-
const memoryVisibilitySchema = z.enum(["private", "public"]);
|
|
28
|
-
const cursorSchema = z
|
|
29
|
-
.object({
|
|
30
|
-
createdAtMs: z.number().finite(),
|
|
31
|
-
id: nonEmptyStringSchema,
|
|
32
|
-
kind: z.enum(MEMORY_KINDS).optional(),
|
|
33
|
-
origin: z.enum(["automatic", "explicit"]).optional(),
|
|
34
|
-
query: z.string().max(200).optional(),
|
|
35
|
-
version: z.literal(1),
|
|
36
|
-
visibility: memoryVisibilitySchema.optional(),
|
|
37
|
-
})
|
|
38
|
-
.strict();
|
|
39
|
-
const pageInputSchema = z
|
|
40
|
-
.object({
|
|
41
|
-
cursor: z.string().min(1).max(1_000).optional(),
|
|
42
|
-
kind: z.enum(MEMORY_KINDS).optional(),
|
|
43
|
-
limit: z.number().int().min(1).max(50),
|
|
44
|
-
origin: z.enum(["automatic", "explicit"]).optional(),
|
|
45
|
-
query: z.string().max(200).optional(),
|
|
46
|
-
visibility: memoryVisibilitySchema.optional(),
|
|
47
|
-
})
|
|
48
|
-
.strict();
|
|
49
|
-
const timelineDaysSchema = z.number().int().min(1).max(365);
|
|
50
|
-
|
|
51
|
-
/** Access label returned by dashboard and REST memory views. */
|
|
52
|
-
export type MemoryVisibility = z.output<typeof memoryVisibilitySchema>;
|
|
53
|
-
|
|
54
|
-
/** Memory fields returned to an authenticated User. */
|
|
55
|
-
export type MemoryView = MemoryRecord & {
|
|
56
|
-
origin: "automatic" | "explicit" | "other";
|
|
57
|
-
sourcePlatform: MemorySourcePlatform;
|
|
58
|
-
visibility: MemoryVisibility;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
interface MemoryPage {
|
|
62
|
-
memories: MemoryView[];
|
|
63
|
-
nextCursor?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
type MemoryPageInput = z.output<typeof pageInputSchema>;
|
|
67
|
-
|
|
68
|
-
/** Expected error for a malformed or mismatched page cursor. */
|
|
69
|
-
export class InvalidMemoryCursorError extends Error {
|
|
70
|
-
constructor() {
|
|
71
|
-
super("Memory cursor is invalid.");
|
|
72
|
-
this.name = "InvalidMemoryCursorError";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Expected error when the current User cannot access a memory. */
|
|
77
|
-
export class MemoryNotFoundError extends Error {
|
|
78
|
-
constructor() {
|
|
79
|
-
super("Memory was not found for this user.");
|
|
80
|
-
this.name = "MemoryNotFoundError";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function publicScopePredicate() {
|
|
85
|
-
return and(
|
|
86
|
-
eq(juniorMemoryMemories.scope, publicMemoryScope.scope),
|
|
87
|
-
eq(juniorMemoryMemories.scopeKey, publicMemoryScope.scopeKey),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function privateScopePredicate(userId: string) {
|
|
92
|
-
return and(
|
|
93
|
-
eq(juniorMemoryMemories.scope, "private"),
|
|
94
|
-
eq(juniorMemoryMemories.scopeKey, userId),
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function visibleScopePredicate(userId: string, visibility?: MemoryVisibility) {
|
|
99
|
-
if (visibility === "public") return publicScopePredicate();
|
|
100
|
-
if (visibility === "private") return privateScopePredicate(userId);
|
|
101
|
-
return or(publicScopePredicate(), privateScopePredicate(userId));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function activeMemoryPredicate(
|
|
105
|
-
userId: string,
|
|
106
|
-
nowMs: number,
|
|
107
|
-
visibility?: MemoryVisibility,
|
|
108
|
-
) {
|
|
109
|
-
return and(
|
|
110
|
-
visibleScopePredicate(userId, visibility),
|
|
111
|
-
isNull(juniorMemoryMemories.archivedAtMs),
|
|
112
|
-
isNull(juniorMemoryMemories.supersededAtMs),
|
|
113
|
-
isNull(juniorMemoryMemories.supersededById),
|
|
114
|
-
or(
|
|
115
|
-
isNull(juniorMemoryMemories.expiresAtMs),
|
|
116
|
-
gt(juniorMemoryMemories.expiresAtMs, nowMs),
|
|
117
|
-
),
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function utcDate(ms: number): string {
|
|
122
|
-
return new Date(ms).toISOString().slice(0, 10);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function searchTerms(query: string): string[] {
|
|
126
|
-
return [
|
|
127
|
-
...new Set(
|
|
128
|
-
query
|
|
129
|
-
.toLowerCase()
|
|
130
|
-
.split(/[^a-z0-9_'-]+/)
|
|
131
|
-
.map((term) => term.trim())
|
|
132
|
-
.filter((term) => term.length >= 2),
|
|
133
|
-
),
|
|
134
|
-
];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function memoryOrigin(idempotencyKey: string | null): MemoryView["origin"] {
|
|
138
|
-
if (idempotencyKey?.startsWith("session:")) return "automatic";
|
|
139
|
-
if (idempotencyKey?.startsWith("tool:")) return "explicit";
|
|
140
|
-
return "other";
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function toMemoryView(
|
|
144
|
-
row: typeof juniorMemoryMemories.$inferSelect,
|
|
145
|
-
): MemoryView {
|
|
146
|
-
const memory = parseMemoryRow(row);
|
|
147
|
-
return {
|
|
148
|
-
...memory,
|
|
149
|
-
origin: memoryOrigin(row.idempotencyKey),
|
|
150
|
-
sourcePlatform: row.sourcePlatform,
|
|
151
|
-
visibility: memory.scope,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function cursorFilters(input: MemoryPageInput) {
|
|
156
|
-
return {
|
|
157
|
-
kind: input.kind,
|
|
158
|
-
origin: input.origin,
|
|
159
|
-
query: input.query?.trim() || undefined,
|
|
160
|
-
visibility: input.visibility,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function decodeCursor(
|
|
165
|
-
value: string | undefined,
|
|
166
|
-
filters: ReturnType<typeof cursorFilters>,
|
|
167
|
-
) {
|
|
168
|
-
if (!value) return undefined;
|
|
169
|
-
try {
|
|
170
|
-
const parsed = cursorSchema.parse(
|
|
171
|
-
JSON.parse(Buffer.from(value, "base64url").toString("utf8")),
|
|
172
|
-
);
|
|
173
|
-
if (
|
|
174
|
-
parsed.query !== filters.query ||
|
|
175
|
-
parsed.kind !== filters.kind ||
|
|
176
|
-
parsed.origin !== filters.origin ||
|
|
177
|
-
parsed.visibility !== filters.visibility
|
|
178
|
-
) {
|
|
179
|
-
throw new InvalidMemoryCursorError();
|
|
180
|
-
}
|
|
181
|
-
return { createdAtMs: parsed.createdAtMs, id: parsed.id };
|
|
182
|
-
} catch {
|
|
183
|
-
throw new InvalidMemoryCursorError();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function encodeCursor(
|
|
188
|
-
createdBefore: { createdAtMs: number; id: string },
|
|
189
|
-
filters: ReturnType<typeof cursorFilters>,
|
|
190
|
-
): string {
|
|
191
|
-
return Buffer.from(
|
|
192
|
-
JSON.stringify({ ...createdBefore, ...filters, version: 1 }),
|
|
193
|
-
"utf8",
|
|
194
|
-
).toString("base64url");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Archive one active private memory owned by the authenticated User. */
|
|
198
|
-
export async function archiveMemory(db: MemoryDb, userId: string, id: string) {
|
|
199
|
-
const memoryId = nonEmptyStringSchema.parse(id);
|
|
200
|
-
const nowMs = Date.now();
|
|
201
|
-
const updated = await db
|
|
202
|
-
.update(juniorMemoryMemories)
|
|
203
|
-
.set({
|
|
204
|
-
archivedAtMs: nowMs,
|
|
205
|
-
archiveReason: "user_removed",
|
|
206
|
-
})
|
|
207
|
-
.where(
|
|
208
|
-
and(
|
|
209
|
-
activeMemoryPredicate(userId, nowMs, "private"),
|
|
210
|
-
eq(juniorMemoryMemories.id, memoryId),
|
|
211
|
-
),
|
|
212
|
-
)
|
|
213
|
-
.returning();
|
|
214
|
-
if (!updated[0]) throw new MemoryNotFoundError();
|
|
215
|
-
await db
|
|
216
|
-
.delete(juniorMemoryEmbeddings)
|
|
217
|
-
.where(eq(juniorMemoryEmbeddings.memoryId, memoryId));
|
|
218
|
-
return parseMemoryRow(updated[0]);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Read one active memory visible to the authenticated User. */
|
|
222
|
-
export async function getMemory(
|
|
223
|
-
db: MemoryDb,
|
|
224
|
-
userId: string,
|
|
225
|
-
id: string,
|
|
226
|
-
): Promise<MemoryView> {
|
|
227
|
-
const memoryId = nonEmptyStringSchema.parse(id);
|
|
228
|
-
const nowMs = Date.now();
|
|
229
|
-
const rows = await db
|
|
230
|
-
.select()
|
|
231
|
-
.from(juniorMemoryMemories)
|
|
232
|
-
.where(
|
|
233
|
-
and(
|
|
234
|
-
activeMemoryPredicate(userId, nowMs),
|
|
235
|
-
eq(juniorMemoryMemories.id, memoryId),
|
|
236
|
-
),
|
|
237
|
-
)
|
|
238
|
-
.limit(1);
|
|
239
|
-
if (!rows[0]) throw new MemoryNotFoundError();
|
|
240
|
-
return toMemoryView(rows[0]);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** List one stable page of active memory visible to the authenticated User. */
|
|
244
|
-
export async function listMemories(
|
|
245
|
-
db: MemoryDb,
|
|
246
|
-
userId: string,
|
|
247
|
-
input: MemoryPageInput,
|
|
248
|
-
): Promise<MemoryPage> {
|
|
249
|
-
input = pageInputSchema.parse(input);
|
|
250
|
-
const filters = cursorFilters(input);
|
|
251
|
-
const cursor = decodeCursor(input.cursor, filters);
|
|
252
|
-
const active = activeMemoryPredicate(userId, Date.now(), input.visibility);
|
|
253
|
-
const createdBefore = cursor
|
|
254
|
-
? or(
|
|
255
|
-
lt(juniorMemoryMemories.createdAtMs, cursor.createdAtMs),
|
|
256
|
-
and(
|
|
257
|
-
eq(juniorMemoryMemories.createdAtMs, cursor.createdAtMs),
|
|
258
|
-
gt(juniorMemoryMemories.id, cursor.id),
|
|
259
|
-
),
|
|
260
|
-
)
|
|
261
|
-
: undefined;
|
|
262
|
-
const terms = input.query ? searchTerms(input.query) : [];
|
|
263
|
-
const search =
|
|
264
|
-
input.query === undefined
|
|
265
|
-
? undefined
|
|
266
|
-
: terms.length === 0
|
|
267
|
-
? sql`false`
|
|
268
|
-
: or(
|
|
269
|
-
...terms.map((term) =>
|
|
270
|
-
ilike(juniorMemoryMemories.content, `%${term}%`),
|
|
271
|
-
),
|
|
272
|
-
);
|
|
273
|
-
const kind = input.kind
|
|
274
|
-
? eq(juniorMemoryMemories.kind, input.kind)
|
|
275
|
-
: undefined;
|
|
276
|
-
const origin =
|
|
277
|
-
input.origin === "automatic"
|
|
278
|
-
? like(juniorMemoryMemories.idempotencyKey, "session:%")
|
|
279
|
-
: input.origin === "explicit"
|
|
280
|
-
? like(juniorMemoryMemories.idempotencyKey, "tool:%")
|
|
281
|
-
: undefined;
|
|
282
|
-
const rows = await db
|
|
283
|
-
.select()
|
|
284
|
-
.from(juniorMemoryMemories)
|
|
285
|
-
.where(and(active, createdBefore, search, kind, origin))
|
|
286
|
-
.orderBy(
|
|
287
|
-
desc(juniorMemoryMemories.createdAtMs),
|
|
288
|
-
asc(juniorMemoryMemories.id),
|
|
289
|
-
)
|
|
290
|
-
.limit(input.limit + 1);
|
|
291
|
-
const memories = rows.slice(0, input.limit).map(toMemoryView);
|
|
292
|
-
const last = memories.at(-1);
|
|
293
|
-
if (rows.length <= input.limit || !last) return { memories };
|
|
294
|
-
const next = { createdAtMs: last.createdAtMs, id: last.id };
|
|
295
|
-
return { memories, nextCursor: encodeCursor(next, filters) };
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** Summarize active memory visible to the authenticated User. */
|
|
299
|
-
export async function getMemoryStats(db: MemoryDb, userId: string) {
|
|
300
|
-
const nowMs = Date.now();
|
|
301
|
-
const [counts] = await db
|
|
302
|
-
.select({
|
|
303
|
-
active: sql<number>`count(*)`.mapWith(Number),
|
|
304
|
-
automatic:
|
|
305
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'session:%')`.mapWith(
|
|
306
|
-
Number,
|
|
307
|
-
),
|
|
308
|
-
createdThirtyDays:
|
|
309
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${nowMs - 30 * DAY_MS})`.mapWith(
|
|
310
|
-
Number,
|
|
311
|
-
),
|
|
312
|
-
embedded: sql<number>`count(${juniorMemoryEmbeddings.memoryId})`.mapWith(
|
|
313
|
-
Number,
|
|
314
|
-
),
|
|
315
|
-
explicit:
|
|
316
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'tool:%')`.mapWith(
|
|
317
|
-
Number,
|
|
318
|
-
),
|
|
319
|
-
knowledge:
|
|
320
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'knowledge')`.mapWith(
|
|
321
|
-
Number,
|
|
322
|
-
),
|
|
323
|
-
private:
|
|
324
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
325
|
-
Number,
|
|
326
|
-
),
|
|
327
|
-
preference:
|
|
328
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'preference')`.mapWith(
|
|
329
|
-
Number,
|
|
330
|
-
),
|
|
331
|
-
procedure:
|
|
332
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
|
|
333
|
-
Number,
|
|
334
|
-
),
|
|
335
|
-
public:
|
|
336
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
337
|
-
Number,
|
|
338
|
-
),
|
|
339
|
-
})
|
|
340
|
-
.from(juniorMemoryMemories)
|
|
341
|
-
.leftJoin(
|
|
342
|
-
juniorMemoryEmbeddings,
|
|
343
|
-
eq(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id),
|
|
344
|
-
)
|
|
345
|
-
.where(activeMemoryPredicate(userId, nowMs));
|
|
346
|
-
if (!counts) throw new Error("Memory stats query returned no row.");
|
|
347
|
-
return counts;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Read daily memory creation totals visible to the authenticated User in UTC. */
|
|
351
|
-
export async function getMemoryTimeline(
|
|
352
|
-
db: MemoryDb,
|
|
353
|
-
userId: string,
|
|
354
|
-
days: number,
|
|
355
|
-
) {
|
|
356
|
-
days = timelineDaysSchema.parse(days);
|
|
357
|
-
const todayMs = Date.parse(`${utcDate(Date.now())}T00:00:00.000Z`);
|
|
358
|
-
const startMs = todayMs - (days - 1) * DAY_MS;
|
|
359
|
-
const rows = await db
|
|
360
|
-
.select({
|
|
361
|
-
date: sql<string>`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`.as(
|
|
362
|
-
"date",
|
|
363
|
-
),
|
|
364
|
-
private:
|
|
365
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
366
|
-
Number,
|
|
367
|
-
),
|
|
368
|
-
public:
|
|
369
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
370
|
-
Number,
|
|
371
|
-
),
|
|
372
|
-
})
|
|
373
|
-
.from(juniorMemoryMemories)
|
|
374
|
-
.where(
|
|
375
|
-
and(
|
|
376
|
-
visibleScopePredicate(userId),
|
|
377
|
-
gt(juniorMemoryMemories.createdAtMs, startMs - 1),
|
|
378
|
-
),
|
|
379
|
-
)
|
|
380
|
-
.groupBy(
|
|
381
|
-
sql`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`,
|
|
382
|
-
);
|
|
383
|
-
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
384
|
-
return Array.from({ length: days }, (_, index) => {
|
|
385
|
-
const date = utcDate(startMs + index * DAY_MS);
|
|
386
|
-
const row = byDate.get(date);
|
|
387
|
-
return {
|
|
388
|
-
date,
|
|
389
|
-
private: row?.private ?? 0,
|
|
390
|
-
public: row?.public ?? 0,
|
|
391
|
-
};
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/** Read hourly memory creation totals visible to the authenticated User in UTC. */
|
|
396
|
-
export async function getMemoryTimelineHours(
|
|
397
|
-
db: MemoryDb,
|
|
398
|
-
userId: string,
|
|
399
|
-
hours = 24,
|
|
400
|
-
) {
|
|
401
|
-
const end = new Date();
|
|
402
|
-
end.setUTCMinutes(0, 0, 0);
|
|
403
|
-
const startMs = end.getTime() - (hours - 1) * 60 * 60 * 1_000;
|
|
404
|
-
const rows = await db
|
|
405
|
-
.select({
|
|
406
|
-
date: sql<string>`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24')`.as(
|
|
407
|
-
"date",
|
|
408
|
-
),
|
|
409
|
-
private:
|
|
410
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'private')`.mapWith(
|
|
411
|
-
Number,
|
|
412
|
-
),
|
|
413
|
-
public:
|
|
414
|
-
sql<number>`count(*) filter (where ${juniorMemoryMemories.scope} = 'public')`.mapWith(
|
|
415
|
-
Number,
|
|
416
|
-
),
|
|
417
|
-
})
|
|
418
|
-
.from(juniorMemoryMemories)
|
|
419
|
-
.where(
|
|
420
|
-
and(
|
|
421
|
-
visibleScopePredicate(userId),
|
|
422
|
-
gt(juniorMemoryMemories.createdAtMs, startMs - 1),
|
|
423
|
-
),
|
|
424
|
-
)
|
|
425
|
-
.groupBy(
|
|
426
|
-
sql`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24')`,
|
|
427
|
-
);
|
|
428
|
-
const byHour = new Map(rows.map((row) => [row.date, row]));
|
|
429
|
-
return Array.from({ length: hours }, (_, index) => {
|
|
430
|
-
const date = new Date(startMs + index * 60 * 60 * 1_000)
|
|
431
|
-
.toISOString()
|
|
432
|
-
.slice(0, 13);
|
|
433
|
-
const row = byHour.get(date);
|
|
434
|
-
return {
|
|
435
|
-
date,
|
|
436
|
-
private: row?.private ?? 0,
|
|
437
|
-
public: row?.public ?? 0,
|
|
438
|
-
};
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|