@sentry/junior-memory 0.127.0 → 0.128.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 +3 -3
- package/dist/api.d.ts +6 -5
- package/dist/index.js +85 -123
- package/dist/index.js.map +1 -1
- package/dist/personal-store.d.ts +7 -4
- package/dist/personal.d.ts +3 -3
- package/dist/scope.d.ts +6 -0
- package/dist/tools.d.ts +0 -130
- package/package.json +2 -2
- package/src/api.ts +18 -23
- package/src/events.ts +1 -13
- package/src/personal-store.ts +11 -45
- package/src/personal.ts +8 -33
- package/src/plugin.ts +1 -1
- package/src/scope.ts +49 -1
- package/src/tools.ts +6 -34
- package/src/user-pages.ts +2 -8
package/dist/personal-store.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { ResolvedMemoryScope } from "./scope";
|
|
2
3
|
import { type MemoryDb, type MemoryRecord } from "./store";
|
|
3
|
-
import { type MemoryRuntimeContext } from "./types";
|
|
4
4
|
declare const memoryVisibilitySchema: z.ZodEnum<{
|
|
5
5
|
public: "public";
|
|
6
6
|
private: "private";
|
|
@@ -68,9 +68,9 @@ export declare class PersonalMemoryNotFoundError extends Error {
|
|
|
68
68
|
}
|
|
69
69
|
/** Viewer-scoped memory operations shared by dashboard and REST. */
|
|
70
70
|
export interface PersonalMemoryCollection {
|
|
71
|
-
/** Archive one exact personal memory owned by a linked
|
|
71
|
+
/** Archive one exact personal memory owned by a linked identity. */
|
|
72
72
|
archive(id: string): Promise<MemoryRecord>;
|
|
73
|
-
/** Read one exact memory visible to a linked
|
|
73
|
+
/** Read one exact memory visible to a linked identity. */
|
|
74
74
|
get(id: string): Promise<PersonalMemoryRecord>;
|
|
75
75
|
/** List one stable page across every authorized viewer scope. */
|
|
76
76
|
list(input: PersonalMemoryPageInput): Promise<PersonalMemoryPage>;
|
|
@@ -82,7 +82,10 @@ export interface PersonalMemoryCollection {
|
|
|
82
82
|
}): Promise<PersonalMemoryDay[]>;
|
|
83
83
|
}
|
|
84
84
|
/** Build storage operations for every memory scope linked to one viewer. */
|
|
85
|
-
export declare function createPersonalMemoryCollection(db: MemoryDb,
|
|
85
|
+
export declare function createPersonalMemoryCollection(db: MemoryDb, scopes: {
|
|
86
|
+
privateScopes: ResolvedMemoryScope[];
|
|
87
|
+
publicScopes: ResolvedMemoryScope[];
|
|
88
|
+
}, options?: {
|
|
86
89
|
now?: () => number;
|
|
87
90
|
}): PersonalMemoryCollection;
|
|
88
91
|
export {};
|
package/dist/personal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { User } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { type MemoryVisibility, type PersonalMemoryRecord } from "./personal-store";
|
|
3
3
|
import type { MemoryDb, MemoryRecord } from "./store";
|
|
4
4
|
import type { MemoryKind } from "./types";
|
|
@@ -19,8 +19,8 @@ export declare class InvalidMemoryCursorError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
export { PersonalMemoryNotFoundError } from "./personal-store";
|
|
21
21
|
export type { MemoryVisibility, PersonalMemoryRecord } from "./personal-store";
|
|
22
|
-
/** Build viewer memory operations authorized by a
|
|
23
|
-
export declare function createViewerMemories(db: MemoryDb,
|
|
22
|
+
/** Build viewer memory operations authorized by a user's linked identities. */
|
|
23
|
+
export declare function createViewerMemories(db: MemoryDb, user: User): {
|
|
24
24
|
archive(id: string): Promise<MemoryRecord>;
|
|
25
25
|
get(id: string): Promise<PersonalMemoryRecord>;
|
|
26
26
|
list(input: ViewerMemoryPageInput): Promise<ViewerMemoryPage>;
|
package/dist/scope.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Identity } from "@sentry/junior-plugin-api";
|
|
1
2
|
import type { MemoryRuntimeContext, MemoryScope, MemorySubjectType } from "./types";
|
|
2
3
|
/** Runtime-derived visibility scope used for memory authorization checks. */
|
|
3
4
|
export interface ResolvedMemoryScope {
|
|
@@ -9,6 +10,11 @@ export interface ResolvedMemorySubject {
|
|
|
9
10
|
subjectKey?: string;
|
|
10
11
|
subjectType: MemorySubjectType;
|
|
11
12
|
}
|
|
13
|
+
/** Derive viewer-visible memory scopes from canonical provider identities. */
|
|
14
|
+
export declare function deriveViewerMemoryScopes(identities: Identity[]): {
|
|
15
|
+
privateScopes: ResolvedMemoryScope[];
|
|
16
|
+
publicScopes: ResolvedMemoryScope[];
|
|
17
|
+
};
|
|
12
18
|
/** Derive the authority-bearing key for a requested memory scope. */
|
|
13
19
|
export declare function deriveMemoryScope(ctx: MemoryRuntimeContext, scope: MemoryScope): ResolvedMemoryScope;
|
|
14
20
|
/** Derive the memory subject from the already-authorized write scope. */
|
package/dist/tools.d.ts
CHANGED
|
@@ -21,19 +21,7 @@ export declare function createMemoryCreateTool(context: MemoryCreateToolContext)
|
|
|
21
21
|
expires_at?: string | undefined;
|
|
22
22
|
}, {
|
|
23
23
|
[x: string]: unknown;
|
|
24
|
-
ok: true;
|
|
25
|
-
status: "success";
|
|
26
24
|
target: string;
|
|
27
|
-
data: {
|
|
28
|
-
created: boolean;
|
|
29
|
-
memory: {
|
|
30
|
-
id: string;
|
|
31
|
-
content: string;
|
|
32
|
-
createdAtMs: number;
|
|
33
|
-
observedAtMs: number;
|
|
34
|
-
expiresAtMs?: number | undefined;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
25
|
created: boolean;
|
|
38
26
|
memory: {
|
|
39
27
|
id: string;
|
|
@@ -47,26 +35,9 @@ export declare function createMemoryCreateTool(context: MemoryCreateToolContext)
|
|
|
47
35
|
arguments: Record<string, unknown>;
|
|
48
36
|
reason?: string | undefined;
|
|
49
37
|
} | undefined;
|
|
50
|
-
error?: string | {
|
|
51
|
-
kind: string;
|
|
52
|
-
message: string;
|
|
53
|
-
retryable?: boolean | undefined;
|
|
54
|
-
} | undefined;
|
|
55
38
|
}, {
|
|
56
39
|
[x: string]: unknown;
|
|
57
|
-
ok: true;
|
|
58
|
-
status: "success";
|
|
59
40
|
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
41
|
created: boolean;
|
|
71
42
|
memory: {
|
|
72
43
|
id: string;
|
|
@@ -80,29 +51,13 @@ export declare function createMemoryCreateTool(context: MemoryCreateToolContext)
|
|
|
80
51
|
arguments: Record<string, unknown>;
|
|
81
52
|
reason?: string | undefined;
|
|
82
53
|
} | undefined;
|
|
83
|
-
error?: string | {
|
|
84
|
-
kind: string;
|
|
85
|
-
message: string;
|
|
86
|
-
retryable?: boolean | undefined;
|
|
87
|
-
} | undefined;
|
|
88
54
|
}>;
|
|
89
55
|
/** Create a tool that archives a visible memory in the active context. */
|
|
90
56
|
export declare function createMemoryRemoveTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
91
57
|
id: string;
|
|
92
58
|
}, {
|
|
93
59
|
[x: string]: unknown;
|
|
94
|
-
ok: true;
|
|
95
|
-
status: "success";
|
|
96
60
|
target: string;
|
|
97
|
-
data: {
|
|
98
|
-
memory: {
|
|
99
|
-
id: string;
|
|
100
|
-
content: string;
|
|
101
|
-
createdAtMs: number;
|
|
102
|
-
observedAtMs: number;
|
|
103
|
-
expiresAtMs?: number | undefined;
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
61
|
memory: {
|
|
107
62
|
id: string;
|
|
108
63
|
content: string;
|
|
@@ -115,25 +70,9 @@ export declare function createMemoryRemoveTool(context: MemoryToolContext): impo
|
|
|
115
70
|
arguments: Record<string, unknown>;
|
|
116
71
|
reason?: string | undefined;
|
|
117
72
|
} | undefined;
|
|
118
|
-
error?: string | {
|
|
119
|
-
kind: string;
|
|
120
|
-
message: string;
|
|
121
|
-
retryable?: boolean | undefined;
|
|
122
|
-
} | undefined;
|
|
123
73
|
}, {
|
|
124
74
|
[x: string]: unknown;
|
|
125
|
-
ok: true;
|
|
126
|
-
status: "success";
|
|
127
75
|
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
76
|
memory: {
|
|
138
77
|
id: string;
|
|
139
78
|
content: string;
|
|
@@ -146,29 +85,13 @@ export declare function createMemoryRemoveTool(context: MemoryToolContext): impo
|
|
|
146
85
|
arguments: Record<string, unknown>;
|
|
147
86
|
reason?: string | undefined;
|
|
148
87
|
} | undefined;
|
|
149
|
-
error?: string | {
|
|
150
|
-
kind: string;
|
|
151
|
-
message: string;
|
|
152
|
-
retryable?: boolean | undefined;
|
|
153
|
-
} | undefined;
|
|
154
88
|
}>;
|
|
155
89
|
/** Create a tool that lists visible active memories in the active context. */
|
|
156
90
|
export declare function createMemoryListTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
157
91
|
limit?: number | undefined;
|
|
158
92
|
}, {
|
|
159
93
|
[x: string]: unknown;
|
|
160
|
-
ok: true;
|
|
161
|
-
status: "success";
|
|
162
94
|
target: string;
|
|
163
|
-
data: {
|
|
164
|
-
memories: {
|
|
165
|
-
id: string;
|
|
166
|
-
content: string;
|
|
167
|
-
createdAtMs: number;
|
|
168
|
-
observedAtMs: number;
|
|
169
|
-
expiresAtMs?: number | undefined;
|
|
170
|
-
}[];
|
|
171
|
-
};
|
|
172
95
|
memories: {
|
|
173
96
|
id: string;
|
|
174
97
|
content: string;
|
|
@@ -181,25 +104,9 @@ export declare function createMemoryListTool(context: MemoryToolContext): import
|
|
|
181
104
|
arguments: Record<string, unknown>;
|
|
182
105
|
reason?: string | undefined;
|
|
183
106
|
} | undefined;
|
|
184
|
-
error?: string | {
|
|
185
|
-
kind: string;
|
|
186
|
-
message: string;
|
|
187
|
-
retryable?: boolean | undefined;
|
|
188
|
-
} | undefined;
|
|
189
107
|
}, {
|
|
190
108
|
[x: string]: unknown;
|
|
191
|
-
ok: true;
|
|
192
|
-
status: "success";
|
|
193
109
|
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
110
|
memories: {
|
|
204
111
|
id: string;
|
|
205
112
|
content: string;
|
|
@@ -212,11 +119,6 @@ export declare function createMemoryListTool(context: MemoryToolContext): import
|
|
|
212
119
|
arguments: Record<string, unknown>;
|
|
213
120
|
reason?: string | undefined;
|
|
214
121
|
} | undefined;
|
|
215
|
-
error?: string | {
|
|
216
|
-
kind: string;
|
|
217
|
-
message: string;
|
|
218
|
-
retryable?: boolean | undefined;
|
|
219
|
-
} | undefined;
|
|
220
122
|
}>;
|
|
221
123
|
/** Create a tool that searches visible active memories in the active context. */
|
|
222
124
|
export declare function createMemorySearchTool(context: MemoryToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
@@ -224,18 +126,7 @@ export declare function createMemorySearchTool(context: MemoryToolContext): impo
|
|
|
224
126
|
limit?: number | undefined;
|
|
225
127
|
}, {
|
|
226
128
|
[x: string]: unknown;
|
|
227
|
-
ok: true;
|
|
228
|
-
status: "success";
|
|
229
129
|
target: string;
|
|
230
|
-
data: {
|
|
231
|
-
memories: {
|
|
232
|
-
id: string;
|
|
233
|
-
content: string;
|
|
234
|
-
createdAtMs: number;
|
|
235
|
-
observedAtMs: number;
|
|
236
|
-
expiresAtMs?: number | undefined;
|
|
237
|
-
}[];
|
|
238
|
-
};
|
|
239
130
|
memories: {
|
|
240
131
|
id: string;
|
|
241
132
|
content: string;
|
|
@@ -248,25 +139,9 @@ export declare function createMemorySearchTool(context: MemoryToolContext): impo
|
|
|
248
139
|
arguments: Record<string, unknown>;
|
|
249
140
|
reason?: string | undefined;
|
|
250
141
|
} | undefined;
|
|
251
|
-
error?: string | {
|
|
252
|
-
kind: string;
|
|
253
|
-
message: string;
|
|
254
|
-
retryable?: boolean | undefined;
|
|
255
|
-
} | undefined;
|
|
256
142
|
}, {
|
|
257
143
|
[x: string]: unknown;
|
|
258
|
-
ok: true;
|
|
259
|
-
status: "success";
|
|
260
144
|
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
145
|
memories: {
|
|
271
146
|
id: string;
|
|
272
147
|
content: string;
|
|
@@ -279,9 +154,4 @@ export declare function createMemorySearchTool(context: MemoryToolContext): impo
|
|
|
279
154
|
arguments: Record<string, unknown>;
|
|
280
155
|
reason?: string | undefined;
|
|
281
156
|
} | undefined;
|
|
282
|
-
error?: string | {
|
|
283
|
-
kind: string;
|
|
284
|
-
message: string;
|
|
285
|
-
retryable?: boolean | undefined;
|
|
286
|
-
} | undefined;
|
|
287
157
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.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.128.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.9.1",
|
package/src/api.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authenticated REST resources for viewer-visible memories.
|
|
3
3
|
*
|
|
4
|
-
* HTTP identity is one verified
|
|
5
|
-
*
|
|
6
|
-
* workspace scopes.
|
|
4
|
+
* HTTP identity is one verified user whose linked identities authorize
|
|
5
|
+
* personal and public workspace scopes.
|
|
7
6
|
*/
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import {
|
|
10
9
|
pluginApiRouteRequestContextSchema,
|
|
11
10
|
type PluginConversationEventStats,
|
|
12
|
-
type PluginApiRouteRequestContext,
|
|
13
11
|
type PluginRouteApp,
|
|
14
|
-
type
|
|
12
|
+
type User,
|
|
15
13
|
} from "@sentry/junior-plugin-api";
|
|
16
14
|
import type { MemoryDb } from "./store";
|
|
17
15
|
import {
|
|
@@ -94,9 +92,11 @@ const memoryListQuerySchema = z
|
|
|
94
92
|
.strict();
|
|
95
93
|
|
|
96
94
|
interface MemoryApiOptions {
|
|
97
|
-
actors(email: string): Promise<PluginUserPageActor[]>;
|
|
98
95
|
db: MemoryDb;
|
|
99
96
|
eventStats: PluginConversationEventStats;
|
|
97
|
+
users: {
|
|
98
|
+
resolve(email: string): Promise<User | undefined>;
|
|
99
|
+
};
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function json(body: unknown, status = 200): Response {
|
|
@@ -122,9 +122,7 @@ function apiMemory(
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function viewerEmail(
|
|
126
|
-
context: PluginApiRouteRequestContext | undefined,
|
|
127
|
-
): string | undefined {
|
|
125
|
+
function viewerEmail(context: unknown): string | undefined {
|
|
128
126
|
const parsed = pluginApiRouteRequestContextSchema.safeParse(context);
|
|
129
127
|
if (!parsed.success || parsed.data.auth.user.emailVerified !== true) {
|
|
130
128
|
return undefined;
|
|
@@ -148,14 +146,17 @@ export function createMemoryApi(options: MemoryApiOptions): PluginRouteApp {
|
|
|
148
146
|
if (!isCollection && !isDashboard && !memoryPath) {
|
|
149
147
|
return json({ error: "Not found." }, 404);
|
|
150
148
|
}
|
|
149
|
+
const isRead = request.method === "GET" || request.method === "HEAD";
|
|
150
|
+
if (!isRead && !(memoryPath && request.method === "DELETE")) {
|
|
151
|
+
return json({ error: "Method not allowed." }, 405);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const user = await options.users.resolve(email);
|
|
155
|
+
if (!user) return json({ error: "Authentication required." }, 401);
|
|
151
156
|
|
|
152
|
-
const
|
|
153
|
-
const memories = createViewerMemories(options.db, actors);
|
|
157
|
+
const memories = createViewerMemories(options.db, user);
|
|
154
158
|
try {
|
|
155
|
-
if (
|
|
156
|
-
isDashboard &&
|
|
157
|
-
(request.method === "GET" || request.method === "HEAD")
|
|
158
|
-
) {
|
|
159
|
+
if (isDashboard && isRead) {
|
|
159
160
|
const [stats, days, extractionDays, recallDays] = await Promise.all([
|
|
160
161
|
memories.stats(),
|
|
161
162
|
memories.timeline({ days: 90 }),
|
|
@@ -183,10 +184,7 @@ export function createMemoryApi(options: MemoryApiOptions): PluginRouteApp {
|
|
|
183
184
|
: json(body);
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
if (
|
|
187
|
-
isCollection &&
|
|
188
|
-
(request.method === "GET" || request.method === "HEAD")
|
|
189
|
-
) {
|
|
187
|
+
if (isCollection && isRead) {
|
|
190
188
|
const query = memoryListQuerySchema.parse({
|
|
191
189
|
cursor: url.searchParams.get("cursor") ?? undefined,
|
|
192
190
|
limit: url.searchParams.get("limit") ?? undefined,
|
|
@@ -209,10 +207,7 @@ export function createMemoryApi(options: MemoryApiOptions): PluginRouteApp {
|
|
|
209
207
|
: json(body);
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
if (
|
|
213
|
-
memoryPath &&
|
|
214
|
-
(request.method === "GET" || request.method === "HEAD")
|
|
215
|
-
) {
|
|
210
|
+
if (memoryPath && isRead) {
|
|
216
211
|
const memory = memoryApiSchema.parse(
|
|
217
212
|
apiMemory(await memories.get(decodeURIComponent(memoryPath[1]!))),
|
|
218
213
|
);
|
package/src/events.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { z } from "zod";
|
|
|
3
3
|
import { MEMORY_KINDS, MEMORY_SCOPES } from "./types";
|
|
4
4
|
import type { MemoryRecord } from "./store";
|
|
5
5
|
|
|
6
|
-
const MAX_EVENT_PREVIEW_CHARS = 500;
|
|
7
|
-
|
|
8
6
|
const capturedMemorySchema = z
|
|
9
7
|
.object({
|
|
10
8
|
content: z.string().min(1),
|
|
@@ -15,12 +13,6 @@ const capturedMemorySchema = z
|
|
|
15
13
|
})
|
|
16
14
|
.strict();
|
|
17
15
|
|
|
18
|
-
function eventPreview(content: string): string {
|
|
19
|
-
const trimmed = content.trim();
|
|
20
|
-
if (trimmed.length <= MAX_EVENT_PREVIEW_CHARS) return trimmed;
|
|
21
|
-
return `${trimmed.slice(0, MAX_EVENT_PREVIEW_CHARS - 3).trimEnd()}...`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
16
|
const capturedMemoriesSchema = z
|
|
25
17
|
.object({
|
|
26
18
|
memories: z.array(capturedMemorySchema).max(100),
|
|
@@ -42,11 +34,7 @@ function renderCapturedMemories(
|
|
|
42
34
|
if (count === 0) return undefined;
|
|
43
35
|
return {
|
|
44
36
|
icon: "brain" as const,
|
|
45
|
-
title: count === 1 ? "
|
|
46
|
-
preview:
|
|
47
|
-
count === 1
|
|
48
|
-
? eventPreview(event.memories[0]!.content)
|
|
49
|
-
: `${count} memories`,
|
|
37
|
+
title: `${count} ${count === 1 ? "memory" : "memories"} captured`,
|
|
50
38
|
details: event.memories.map((memory) => ({
|
|
51
39
|
title: memory.content,
|
|
52
40
|
metadata: [memory.kind, memory.scope],
|
package/src/personal-store.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SQL operations over memories visible to one authenticated viewer.
|
|
3
3
|
*
|
|
4
|
-
* A
|
|
5
|
-
* personal
|
|
4
|
+
* A user may have several linked identities. This store combines their
|
|
5
|
+
* identity-scoped personal memories with authorized public workspace 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";
|
|
9
9
|
import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema";
|
|
10
|
-
import {
|
|
10
|
+
import type { ResolvedMemoryScope } from "./scope";
|
|
11
11
|
import {
|
|
12
12
|
activeVisiblePredicate,
|
|
13
13
|
archiveExpiredMemoryBatch,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type MemoryDb,
|
|
16
16
|
type MemoryRecord,
|
|
17
17
|
} from "./store";
|
|
18
|
-
import { MEMORY_KINDS
|
|
18
|
+
import { MEMORY_KINDS } from "./types";
|
|
19
19
|
|
|
20
20
|
const nonEmptyStringSchema = z.string().min(1);
|
|
21
21
|
const memoryVisibilitySchema = z.enum(["private", "public"]);
|
|
@@ -93,9 +93,9 @@ export class PersonalMemoryNotFoundError extends Error {
|
|
|
93
93
|
|
|
94
94
|
/** Viewer-scoped memory operations shared by dashboard and REST. */
|
|
95
95
|
export interface PersonalMemoryCollection {
|
|
96
|
-
/** Archive one exact personal memory owned by a linked
|
|
96
|
+
/** Archive one exact personal memory owned by a linked identity. */
|
|
97
97
|
archive(id: string): Promise<MemoryRecord>;
|
|
98
|
-
/** Read one exact memory visible to a linked
|
|
98
|
+
/** Read one exact memory visible to a linked identity. */
|
|
99
99
|
get(id: string): Promise<PersonalMemoryRecord>;
|
|
100
100
|
/** List one stable page across every authorized viewer scope. */
|
|
101
101
|
list(input: PersonalMemoryPageInput): Promise<PersonalMemoryPage>;
|
|
@@ -105,42 +105,6 @@ export interface PersonalMemoryCollection {
|
|
|
105
105
|
timeline(input: { days: number }): Promise<PersonalMemoryDay[]>;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function personalScopes(
|
|
109
|
-
runtimeContexts: MemoryRuntimeContext[],
|
|
110
|
-
): ResolvedMemoryScope[] {
|
|
111
|
-
return [
|
|
112
|
-
...new Map(
|
|
113
|
-
runtimeContexts.map((context) => {
|
|
114
|
-
const scope = deriveMemoryScope(context, "personal");
|
|
115
|
-
return [`${scope.scope}:${scope.scopeKey}`, scope] as const;
|
|
116
|
-
}),
|
|
117
|
-
).values(),
|
|
118
|
-
];
|
|
119
|
-
}
|
|
120
|
-
|
|
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
108
|
function scopePredicate(scopes: ResolvedMemoryScope[]) {
|
|
145
109
|
if (scopes.length === 0) return undefined;
|
|
146
110
|
return or(
|
|
@@ -213,11 +177,13 @@ function emptyStats(): PersonalMemoryStats {
|
|
|
213
177
|
/** Build storage operations for every memory scope linked to one viewer. */
|
|
214
178
|
export function createPersonalMemoryCollection(
|
|
215
179
|
db: MemoryDb,
|
|
216
|
-
|
|
180
|
+
scopes: {
|
|
181
|
+
privateScopes: ResolvedMemoryScope[];
|
|
182
|
+
publicScopes: ResolvedMemoryScope[];
|
|
183
|
+
},
|
|
217
184
|
options: { now?: () => number } = {},
|
|
218
185
|
): PersonalMemoryCollection {
|
|
219
|
-
const privateScopes =
|
|
220
|
-
const publicScopes = publicWorkspaceScopes(runtimeContexts);
|
|
186
|
+
const { privateScopes, publicScopes } = scopes;
|
|
221
187
|
const allScopes = [...privateScopes, ...publicScopes];
|
|
222
188
|
const getNowMs = () => options.now?.() ?? Date.now();
|
|
223
189
|
|
package/src/personal.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authenticated-viewer memory access shared by REST and dashboard projections.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* authorized public workspace scopes.
|
|
4
|
+
* One user may have multiple provider identities. This module adapts those
|
|
5
|
+
* identities to the existing personal and public workspace scopes.
|
|
7
6
|
*/
|
|
8
7
|
import { z } from "zod";
|
|
9
|
-
import type {
|
|
8
|
+
import type { User } from "@sentry/junior-plugin-api";
|
|
10
9
|
import {
|
|
11
10
|
createPersonalMemoryCollection,
|
|
12
11
|
type MemoryVisibility,
|
|
13
12
|
type PersonalMemoryRecord,
|
|
14
13
|
} from "./personal-store";
|
|
14
|
+
import { deriveViewerMemoryScopes } from "./scope";
|
|
15
15
|
import type { MemoryDb, MemoryRecord } from "./store";
|
|
16
|
-
import type { MemoryKind
|
|
16
|
+
import type { MemoryKind } from "./types";
|
|
17
17
|
|
|
18
18
|
const cursorSchema = z
|
|
19
19
|
.object({
|
|
@@ -51,28 +51,6 @@ export class InvalidMemoryCursorError extends Error {
|
|
|
51
51
|
export { PersonalMemoryNotFoundError } from "./personal-store";
|
|
52
52
|
export type { MemoryVisibility, PersonalMemoryRecord } from "./personal-store";
|
|
53
53
|
|
|
54
|
-
function runtimeContext(actor: PluginUserPageActor): MemoryRuntimeContext {
|
|
55
|
-
if (actor.platform === "slack") {
|
|
56
|
-
return {
|
|
57
|
-
actor,
|
|
58
|
-
source: {
|
|
59
|
-
platform: "slack",
|
|
60
|
-
visibility: "private",
|
|
61
|
-
teamId: actor.teamId,
|
|
62
|
-
channelId: "DDASHBOARD",
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
actor,
|
|
68
|
-
source: {
|
|
69
|
-
platform: "local",
|
|
70
|
-
visibility: "private",
|
|
71
|
-
conversationId: "local:dashboard:memories",
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
54
|
function decodeCursor(
|
|
77
55
|
value: string | undefined,
|
|
78
56
|
input: Pick<
|
|
@@ -119,14 +97,11 @@ function encodeCursor(
|
|
|
119
97
|
).toString("base64url");
|
|
120
98
|
}
|
|
121
99
|
|
|
122
|
-
/** Build viewer memory operations authorized by a
|
|
123
|
-
export function createViewerMemories(
|
|
124
|
-
db: MemoryDb,
|
|
125
|
-
actors: PluginUserPageActor[],
|
|
126
|
-
) {
|
|
100
|
+
/** Build viewer memory operations authorized by a user's linked identities. */
|
|
101
|
+
export function createViewerMemories(db: MemoryDb, user: User) {
|
|
127
102
|
const collection = createPersonalMemoryCollection(
|
|
128
103
|
db,
|
|
129
|
-
|
|
104
|
+
deriveViewerMemoryScopes(user.identities),
|
|
130
105
|
);
|
|
131
106
|
return {
|
|
132
107
|
async archive(id: string): Promise<MemoryRecord> {
|
package/src/plugin.ts
CHANGED
|
@@ -135,9 +135,9 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) {
|
|
|
135
135
|
},
|
|
136
136
|
apiRoutes(ctx) {
|
|
137
137
|
return createMemoryApi({
|
|
138
|
-
actors: ctx.viewer.actors,
|
|
139
138
|
db: ctx.db as MemoryDb,
|
|
140
139
|
eventStats: ctx.eventStats,
|
|
140
|
+
users: ctx.users,
|
|
141
141
|
});
|
|
142
142
|
},
|
|
143
143
|
tools(ctx) {
|
package/src/scope.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPrivateSource } from "@sentry/junior-plugin-api";
|
|
1
|
+
import { isPrivateSource, type Identity } from "@sentry/junior-plugin-api";
|
|
2
2
|
import type {
|
|
3
3
|
MemoryRuntimeContext,
|
|
4
4
|
MemoryScope,
|
|
@@ -17,6 +17,54 @@ export interface ResolvedMemorySubject {
|
|
|
17
17
|
subjectType: MemorySubjectType;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function uniqueScopes(scopes: ResolvedMemoryScope[]): ResolvedMemoryScope[] {
|
|
21
|
+
return [
|
|
22
|
+
...new Map(
|
|
23
|
+
scopes.map((scope) => [`${scope.scope}:${scope.scopeKey}`, scope]),
|
|
24
|
+
).values(),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Derive viewer-visible memory scopes from canonical provider identities. */
|
|
29
|
+
export function deriveViewerMemoryScopes(identities: Identity[]): {
|
|
30
|
+
privateScopes: ResolvedMemoryScope[];
|
|
31
|
+
publicScopes: ResolvedMemoryScope[];
|
|
32
|
+
} {
|
|
33
|
+
const privateScopes = identities.flatMap((identity) => {
|
|
34
|
+
if (identity.provider === "local") {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
scope: "personal" as const,
|
|
38
|
+
scopeKey: `local:${identity.providerSubjectId}`,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
if (identity.provider === "slack" && identity.providerTenantId) {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
scope: "personal" as const,
|
|
46
|
+
scopeKey: `slack:${identity.providerTenantId}:${identity.providerSubjectId}`,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
});
|
|
52
|
+
const publicScopes = identities.flatMap((identity) =>
|
|
53
|
+
identity.provider === "slack" && identity.providerTenantId
|
|
54
|
+
? [
|
|
55
|
+
{
|
|
56
|
+
scope: "conversation" as const,
|
|
57
|
+
scopeKey: `slack:${identity.providerTenantId}`,
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: [],
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
privateScopes: uniqueScopes(privateScopes),
|
|
64
|
+
publicScopes: uniqueScopes(publicScopes),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
20
68
|
function sourceConversationKey(ctx: MemoryRuntimeContext): string | undefined {
|
|
21
69
|
if (ctx.source.platform === "local") {
|
|
22
70
|
return ctx.source.conversationId;
|