@sentry/junior-memory 0.116.1 → 0.118.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/recall.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { PromptMessage, Actor, Source } from "@sentry/junior-plugin-api";
1
+ import { type UserPromptContribution, type Actor, type Source } from "@sentry/junior-plugin-api";
2
+ import { z } from "zod";
2
3
  import { type MemoryDb, type MemoryEmbeddingProvider } from "./store";
3
4
  export interface MemoryRecallContext {
4
5
  conversationId?: string;
@@ -10,5 +11,27 @@ export interface MemoryRecallContext {
10
11
  source: Source;
11
12
  text: string;
12
13
  }
13
- /** Build the memory prompt contribution for active visible recall. */
14
- export declare function createMemoryPromptMessages(context: MemoryRecallContext): Promise<PromptMessage[] | undefined>;
14
+ /** Structured snapshot retained for one automatic memory recall. */
15
+ export declare const memoryRecallContextSchema: z.ZodObject<{
16
+ memories: z.ZodArray<z.ZodObject<{
17
+ id: z.ZodString;
18
+ content: z.ZodString;
19
+ observedAtMs: z.ZodNumber;
20
+ scope: z.ZodEnum<{
21
+ personal: "personal";
22
+ conversation: "conversation";
23
+ }>;
24
+ kind: z.ZodEnum<{
25
+ preference: "preference";
26
+ procedure: "procedure";
27
+ knowledge: "knowledge";
28
+ }>;
29
+ }, z.core.$strict>>;
30
+ }, z.core.$strict>;
31
+ /**
32
+ * Build active memory recall contributions.
33
+ *
34
+ * Personal recall remains prompt-only so conversation reporting cannot widen
35
+ * actor-scoped memory visibility.
36
+ */
37
+ export declare function createMemoryPromptContributions(context: MemoryRecallContext): Promise<UserPromptContribution[] | undefined>;
package/dist/store.d.ts CHANGED
@@ -166,6 +166,8 @@ export interface MemoryStore {
166
166
  createConversationMemory(input: CreateMemoryInput): Promise<CreateMemoryResult>;
167
167
  /** List active memories visible in the current runtime context. */
168
168
  listMemories(input: ListMemoriesInput): Promise<MemoryRecord[]>;
169
+ /** List active personal memories owned by the current actor. */
170
+ listPersonalMemories(input: ListMemoriesInput): Promise<MemoryRecord[]>;
169
171
  /** Search active memories visible in the current runtime context. */
170
172
  searchMemories(input: SearchMemoriesInput): Promise<MemoryRecord[]>;
171
173
  }
@@ -0,0 +1,4 @@
1
+ /** Project actor-owned memories into Junior's core-rendered user page. */
2
+ import type { PluginUserPageDefinition } from "@sentry/junior-plugin-api";
3
+ /** Create the read-only personal Memories dashboard page. */
4
+ export declare function createMemoryUserPage(): PluginUserPageDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-memory",
3
- "version": "0.116.1",
3
+ "version": "0.118.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.116.1"
30
+ "@sentry/junior-plugin-api": "0.118.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.9.1",
package/src/plugin.ts CHANGED
@@ -11,8 +11,9 @@ import {
11
11
  type MemoryToolContext,
12
12
  } from "./tools";
13
13
  import { processMemorySession } from "./process-session";
14
- import { createMemoryPromptMessages } from "./recall";
14
+ import { createMemoryPromptContributions } from "./recall";
15
15
  import type { MemoryDb } from "./store";
16
+ import { createMemoryUserPage } from "./user-pages";
16
17
 
17
18
  const MEMORY_MODEL_ENV = "AI_MEMORY_MODEL";
18
19
  const MEMORY_RECALL_MAX_VECTOR_DISTANCE_ENV =
@@ -107,6 +108,7 @@ export function createMemoryPlugin(options: MemoryPluginOptions = {}) {
107
108
  },
108
109
  },
109
110
  },
111
+ userPages: [createMemoryUserPage()],
110
112
  hooks: {
111
113
  tools(ctx) {
112
114
  const agent = createMemoryAgent(ctx.model);
@@ -132,7 +134,7 @@ export function createMemoryPlugin(options: MemoryPluginOptions = {}) {
132
134
  };
133
135
  },
134
136
  async userPrompt(ctx) {
135
- return await createMemoryPromptMessages({
137
+ return await createMemoryPromptContributions({
136
138
  ...(ctx.conversationId ? { conversationId: ctx.conversationId } : {}),
137
139
  ...(ctx.actor ? { actor: ctx.actor } : {}),
138
140
  db: ctx.db as MemoryDb,
package/src/recall.ts CHANGED
@@ -1,4 +1,10 @@
1
- import type { PromptMessage, Actor, Source } from "@sentry/junior-plugin-api";
1
+ import {
2
+ definePromptContext,
3
+ type UserPromptContribution,
4
+ type Actor,
5
+ type Source,
6
+ } from "@sentry/junior-plugin-api";
7
+ import { z } from "zod";
2
8
  import {
3
9
  createMemoryStore,
4
10
  type MemoryDb,
@@ -34,35 +40,78 @@ function formatObservedDate(observedAtMs: number): string {
34
40
  return new Date(observedAtMs).toISOString().slice(0, 10);
35
41
  }
36
42
 
37
- function renderMemoryPrompt(memories: MemoryRecord[]): string | undefined {
43
+ const recalledMemorySchema = z
44
+ .object({
45
+ id: z.string().min(1),
46
+ content: z.string().min(1).max(MAX_MEMORY_LINE_CHARS),
47
+ observedAtMs: z.number().finite(),
48
+ scope: z.enum(["personal", "conversation"]),
49
+ kind: z.enum(["preference", "procedure", "knowledge"]),
50
+ })
51
+ .strict();
52
+
53
+ /** Structured snapshot retained for one automatic memory recall. */
54
+ export const memoryRecallContextSchema = z
55
+ .object({
56
+ memories: z.array(recalledMemorySchema).min(1).max(DEFAULT_RECALL_LIMIT),
57
+ })
58
+ .strict();
59
+
60
+ type RecalledMemory = z.output<typeof recalledMemorySchema>;
61
+
62
+ function selectPromptMemories(memories: MemoryRecord[]): RecalledMemory[] {
38
63
  const header = "Relevant memories for this request:";
39
64
  const footer =
40
65
  "Treat these as possibly stale context. Current user instructions and repository evidence take priority.";
41
- const lines: string[] = [];
66
+ const selected: RecalledMemory[] = [];
42
67
  let totalChars = header.length + footer.length + 2;
43
68
 
44
69
  for (const memory of memories) {
45
- const line = `- Observed ${formatObservedDate(memory.observedAtMs)}: ${trimContent(
46
- memory.content,
47
- MAX_MEMORY_LINE_CHARS,
48
- )}`;
70
+ const content = trimContent(memory.content, MAX_MEMORY_LINE_CHARS);
71
+ const line = `- Observed ${formatObservedDate(memory.observedAtMs)}: ${content}`;
49
72
  if (totalChars + line.length + 1 > MAX_PROMPT_CHARS) {
50
73
  break;
51
74
  }
52
- lines.push(line);
75
+ selected.push({
76
+ id: memory.id,
77
+ content,
78
+ observedAtMs: memory.observedAtMs,
79
+ scope: memory.scope,
80
+ kind: memory.kind,
81
+ });
53
82
  totalChars += line.length + 1;
54
83
  }
84
+ return selected;
85
+ }
55
86
 
56
- if (lines.length === 0) {
57
- return undefined;
58
- }
59
- return `${header}\n${lines.join("\n")}\n\n${footer}`;
87
+ function renderMemoryPrompt(memories: RecalledMemory[]): string {
88
+ return [
89
+ "Relevant memories for this request:",
90
+ ...memories.map(
91
+ (memory) =>
92
+ `- Observed ${formatObservedDate(memory.observedAtMs)}: ${memory.content}`,
93
+ ),
94
+ "",
95
+ "Treat these as possibly stale context. Current user instructions and repository evidence take priority.",
96
+ ].join("\n");
60
97
  }
61
98
 
62
- /** Build the memory prompt contribution for active visible recall. */
63
- export async function createMemoryPromptMessages(
99
+ const memoryRecallContext = definePromptContext({
100
+ kind: "recall",
101
+ version: 1,
102
+ schema: memoryRecallContextSchema,
103
+ renderPrompt: (content) => renderMemoryPrompt(content.memories),
104
+ });
105
+
106
+ /**
107
+ * Build active memory recall contributions.
108
+ *
109
+ * Personal recall remains prompt-only so conversation reporting cannot widen
110
+ * actor-scoped memory visibility.
111
+ */
112
+ export async function createMemoryPromptContributions(
64
113
  context: MemoryRecallContext,
65
- ): Promise<PromptMessage[] | undefined> {
114
+ ): Promise<UserPromptContribution[] | undefined> {
66
115
  if (!context.text.trim()) {
67
116
  return undefined;
68
117
  }
@@ -82,6 +131,12 @@ export async function createMemoryPromptMessages(
82
131
  query: context.text,
83
132
  limit: DEFAULT_RECALL_LIMIT,
84
133
  });
85
- const text = renderMemoryPrompt(memories);
86
- return text ? [{ text }] : undefined;
134
+ const selected = selectPromptMemories(memories);
135
+ if (selected.length === 0) {
136
+ return undefined;
137
+ }
138
+ if (selected.some((memory) => memory.scope === "personal")) {
139
+ return [{ text: renderMemoryPrompt(selected) }];
140
+ }
141
+ return [memoryRecallContext({ memories: selected })];
87
142
  }
package/src/store.ts CHANGED
@@ -324,6 +324,8 @@ export interface MemoryStore {
324
324
  ): Promise<CreateMemoryResult>;
325
325
  /** List active memories visible in the current runtime context. */
326
326
  listMemories(input: ListMemoriesInput): Promise<MemoryRecord[]>;
327
+ /** List active personal memories owned by the current actor. */
328
+ listPersonalMemories(input: ListMemoriesInput): Promise<MemoryRecord[]>;
327
329
  /** Search active memories visible in the current runtime context. */
328
330
  searchMemories(input: SearchMemoriesInput): Promise<MemoryRecord[]>;
329
331
  }
@@ -1468,6 +1470,23 @@ export function createMemoryStore(
1468
1470
  });
1469
1471
  },
1470
1472
 
1473
+ async listPersonalMemories(input) {
1474
+ input = listMemoriesInputSchema.parse(input);
1475
+ const nowMs = getNowMs();
1476
+ const scopes = [deriveMemoryScope(runtimeContext, "personal")];
1477
+ await archiveExpiredMemoryBatch({
1478
+ db,
1479
+ nowMs,
1480
+ scopes,
1481
+ });
1482
+ return await listVisibleMemories({
1483
+ db,
1484
+ limit: input.limit,
1485
+ nowMs,
1486
+ scopes,
1487
+ });
1488
+ },
1489
+
1471
1490
  async searchMemories(input) {
1472
1491
  input = searchMemoriesInputSchema.parse(input);
1473
1492
  const nowMs = getNowMs();
@@ -0,0 +1,94 @@
1
+ /** Project actor-owned memories into Junior's core-rendered user page. */
2
+ import type {
3
+ PluginUserPageActor,
4
+ PluginUserPageDefinition,
5
+ } from "@sentry/junior-plugin-api";
6
+ import { createMemoryStore, type MemoryDb, type MemoryRecord } from "./store";
7
+ import type { MemoryRuntimeContext } from "./types";
8
+
9
+ const MEMORY_PAGE_LIMIT = 100;
10
+
11
+ function runtimeContext(
12
+ actor: PluginUserPageActor,
13
+ ): MemoryRuntimeContext | undefined {
14
+ if (actor.platform === "slack") {
15
+ return {
16
+ actor,
17
+ source: {
18
+ platform: "slack",
19
+ type: "priv",
20
+ teamId: actor.teamId,
21
+ channelId: "DDASHBOARD",
22
+ },
23
+ };
24
+ }
25
+ return {
26
+ actor,
27
+ source: {
28
+ platform: "local",
29
+ type: "priv",
30
+ conversationId: "local:dashboard:memories",
31
+ },
32
+ };
33
+ }
34
+
35
+ function memoryKindLabel(kind: MemoryRecord["kind"]): string {
36
+ return kind.charAt(0).toUpperCase() + kind.slice(1);
37
+ }
38
+
39
+ function rememberedDate(createdAtMs: number): string {
40
+ return new Intl.DateTimeFormat("en-US", {
41
+ dateStyle: "medium",
42
+ timeZone: "UTC",
43
+ }).format(new Date(createdAtMs));
44
+ }
45
+
46
+ async function readPersonalMemories(
47
+ db: MemoryDb,
48
+ actors: PluginUserPageActor[],
49
+ ): Promise<MemoryRecord[]> {
50
+ const memories = await Promise.all(
51
+ actors.flatMap((actor) => {
52
+ const context = runtimeContext(actor);
53
+ return context
54
+ ? [
55
+ createMemoryStore(db, context).listPersonalMemories({
56
+ limit: MEMORY_PAGE_LIMIT,
57
+ }),
58
+ ]
59
+ : [];
60
+ }),
61
+ );
62
+ return [
63
+ ...new Map(memories.flat().map((memory) => [memory.id, memory])).values(),
64
+ ]
65
+ .sort((left, right) => right.createdAtMs - left.createdAtMs)
66
+ .slice(0, MEMORY_PAGE_LIMIT);
67
+ }
68
+
69
+ /** Create the read-only personal Memories dashboard page. */
70
+ export function createMemoryUserPage(): PluginUserPageDefinition {
71
+ return {
72
+ id: "memories",
73
+ label: "Memories",
74
+ description: "Personal facts and preferences Junior remembers about you.",
75
+ async read(ctx) {
76
+ const memories = await readPersonalMemories(
77
+ ctx.db as MemoryDb,
78
+ ctx.viewer.actors,
79
+ );
80
+ return {
81
+ type: "list",
82
+ emptyText: "No personal memories yet.",
83
+ records: memories.map((memory) => ({
84
+ id: memory.id,
85
+ title: memory.content,
86
+ metadata: [
87
+ { label: "Type", value: memoryKindLabel(memory.kind) },
88
+ { label: "Remembered", value: rememberedDate(memory.createdAtMs) },
89
+ ],
90
+ })),
91
+ };
92
+ },
93
+ };
94
+ }