@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/README.md +2 -0
- package/dist/index.js +161 -37
- package/dist/index.js.map +1 -1
- package/dist/recall.d.ts +26 -3
- package/dist/store.d.ts +2 -0
- package/dist/user-pages.d.ts +4 -0
- package/package.json +2 -2
- package/src/plugin.ts +4 -2
- package/src/recall.ts +72 -17
- package/src/store.ts +19 -0
- package/src/user-pages.ts +94 -0
package/dist/recall.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
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
|
-
/**
|
|
14
|
-
export declare
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
66
|
+
const selected: RecalledMemory[] = [];
|
|
42
67
|
let totalChars = header.length + footer.length + 2;
|
|
43
68
|
|
|
44
69
|
for (const memory of memories) {
|
|
45
|
-
const
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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<
|
|
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
|
|
86
|
-
|
|
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
|
+
}
|