@sentry/junior-memory 0.127.0 → 0.128.1

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.
@@ -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 actor. */
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 actor. */
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, runtimeContexts: MemoryRuntimeContext[], options?: {
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 {};
@@ -1,4 +1,4 @@
1
- import type { PluginUserPageActor } from "@sentry/junior-plugin-api";
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 viewer's linked actors. */
23
- export declare function createViewerMemories(db: MemoryDb, actors: PluginUserPageActor[]): {
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.127.0",
3
+ "version": "0.128.1",
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.127.0"
30
+ "@sentry/junior-plugin-api": "0.128.1"
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 viewer. Linked platform actors are resolved
5
- * behind the route boundary and used only to authorize personal and public
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 PluginUserPageActor,
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 actors = await options.actors(email);
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 ? "Memory captured" : "Memories captured",
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],
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * SQL operations over memories visible to one authenticated viewer.
3
3
  *
4
- * A viewer may resolve to several runtime actors. This store combines their
5
- * personal scopes with authorized public workspace conversation scopes.
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 { deriveMemoryScope, type ResolvedMemoryScope } from "./scope";
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, type MemoryRuntimeContext } from "./types";
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 actor. */
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 actor. */
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
- runtimeContexts: MemoryRuntimeContext[],
180
+ scopes: {
181
+ privateScopes: ResolvedMemoryScope[];
182
+ publicScopes: ResolvedMemoryScope[];
183
+ },
217
184
  options: { now?: () => number } = {},
218
185
  ): PersonalMemoryCollection {
219
- const privateScopes = personalScopes(runtimeContexts);
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
- * Viewer identity may resolve to multiple platform actors. This module keeps
5
- * that federation behind one viewer-memory collection spanning personal and
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 { PluginUserPageActor } from "@sentry/junior-plugin-api";
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, MemoryRuntimeContext } from "./types";
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 viewer's linked actors. */
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
- actors.map(runtimeContext),
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;