@sentry/junior-memory 0.122.1 → 0.124.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.
@@ -0,0 +1,19 @@
1
+ import type { MemoryRecord } from "./store";
2
+ /** Durable transcript event emitted after background memory capture. */
3
+ export declare const memoriesCapturedEvent: import("@sentry/junior-plugin-api").DefinedConversationEvent<{
4
+ memories: {
5
+ content: string;
6
+ id: string;
7
+ kind: "preference" | "procedure" | "knowledge";
8
+ observedAtMs: number;
9
+ scope: "personal" | "conversation";
10
+ }[];
11
+ }>;
12
+ /** Select the stable, safe memory fields retained in conversation history. */
13
+ export declare function capturedMemory(memory: MemoryRecord): {
14
+ content: string;
15
+ id: string;
16
+ kind: "preference" | "procedure" | "knowledge";
17
+ observedAtMs: number;
18
+ scope: "personal" | "conversation";
19
+ };
package/dist/index.js CHANGED
@@ -527,7 +527,7 @@ async function findByIdempotencyKey(args) {
527
527
  )
528
528
  ).limit(1);
529
529
  if (activeRows[0]) {
530
- return parseMemoryRow(activeRows[0]);
530
+ return { memory: parseMemoryRow(activeRows[0]), outcome: "created" };
531
531
  }
532
532
  const aliasRows = await args.db.select({ supersededById: juniorMemoryMemories.supersededById }).from(juniorMemoryMemories).where(
533
533
  and(
@@ -565,7 +565,7 @@ async function findByIdempotencyKey(args) {
565
565
  )
566
566
  ).limit(1);
567
567
  if (rows[0]) {
568
- return parseMemoryRow(rows[0]);
568
+ return { memory: parseMemoryRow(rows[0]), outcome: "duplicate" };
569
569
  }
570
570
  }
571
571
  return void 0;
@@ -1023,13 +1023,13 @@ function createMemoryStore(db, context, options = {}) {
1023
1023
  });
1024
1024
  if (idempotent2) {
1025
1025
  await storeEmbedding({
1026
- content: idempotent2.content,
1026
+ content: idempotent2.memory.content,
1027
1027
  db,
1028
1028
  embedder,
1029
- memoryId: idempotent2.id,
1029
+ memoryId: idempotent2.memory.id,
1030
1030
  nowMs
1031
1031
  });
1032
- return { created: false, memory: idempotent2 };
1032
+ return idempotent2.outcome === "created" ? { created: false, idempotent: true, memory: idempotent2.memory } : { created: false, memory: idempotent2.memory };
1033
1033
  }
1034
1034
  }
1035
1035
  const exactDuplicate = await findExactDuplicateMemory({
@@ -1088,7 +1088,7 @@ function createMemoryStore(db, context, options = {}) {
1088
1088
  }
1089
1089
  }
1090
1090
  const id = randomUUID();
1091
- const rows = await db.transaction(async (tx) => {
1091
+ const write = await db.transaction(async (tx) => {
1092
1092
  const inserted = await tx.insert(juniorMemoryMemories).values({
1093
1093
  content,
1094
1094
  createdAtMs: nowMs,
@@ -1113,7 +1113,7 @@ function createMemoryStore(db, context, options = {}) {
1113
1113
  }).returning();
1114
1114
  const insertedMemory = inserted[0];
1115
1115
  if (!insertedMemory || supersededIds.length === 0) {
1116
- return inserted;
1116
+ return { inserted, supersededIds: [] };
1117
1117
  }
1118
1118
  const superseded = await tx.update(juniorMemoryMemories).set({
1119
1119
  supersededAtMs: nowMs,
@@ -1133,10 +1133,10 @@ function createMemoryStore(db, context, options = {}) {
1133
1133
  if (idsToClean.length > 0) {
1134
1134
  await tx.delete(juniorMemoryEmbeddings).where(inArray(juniorMemoryEmbeddings.memoryId, idsToClean));
1135
1135
  }
1136
- return inserted;
1136
+ return { inserted, supersededIds: idsToClean };
1137
1137
  });
1138
- if (rows[0]) {
1139
- const memory = parseMemoryRow(rows[0]);
1138
+ if (write.inserted[0]) {
1139
+ const memory = parseMemoryRow(write.inserted[0]);
1140
1140
  await storeEmbedding({
1141
1141
  content: memory.content,
1142
1142
  db,
@@ -1145,7 +1145,11 @@ function createMemoryStore(db, context, options = {}) {
1145
1145
  memoryId: memory.id,
1146
1146
  nowMs
1147
1147
  });
1148
- return { created: true, memory };
1148
+ return {
1149
+ created: true,
1150
+ memory,
1151
+ ...write.supersededIds.length > 0 ? { supersededIds: write.supersededIds } : {}
1152
+ };
1149
1153
  }
1150
1154
  const idempotent = await findByIdempotencyKey({
1151
1155
  db,
@@ -1157,13 +1161,13 @@ function createMemoryStore(db, context, options = {}) {
1157
1161
  throw new Error("Memory idempotency conflict did not resolve.");
1158
1162
  }
1159
1163
  await storeEmbedding({
1160
- content: idempotent.content,
1164
+ content: idempotent.memory.content,
1161
1165
  db,
1162
1166
  embedder,
1163
- memoryId: idempotent.id,
1167
+ memoryId: idempotent.memory.id,
1164
1168
  nowMs
1165
1169
  });
1166
- return { created: false, memory: idempotent };
1170
+ return idempotent.outcome === "created" ? { created: false, idempotent: true, memory: idempotent.memory } : { created: false, memory: idempotent.memory };
1167
1171
  }
1168
1172
  async function retrieveVisibleMemories(rawInput, vectorMaxDistance) {
1169
1173
  const input = searchMemoriesInputSchema.parse(rawInput);
@@ -2438,6 +2442,12 @@ function memoryToolResult(target, data) {
2438
2442
  }
2439
2443
  function createMemoryCreateTool(context) {
2440
2444
  return definePluginTool({
2445
+ annotations: {
2446
+ destructiveHint: false,
2447
+ idempotentHint: true,
2448
+ openWorldHint: false,
2449
+ readOnlyHint: false
2450
+ },
2441
2451
  description: "Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a public/shareable fact. Do not use for ordinary statements like 'I prefer X', 'I use Y', or 'X goes before Y' unless the user also asks you to remember/store/save it; passive memory learning handles those after the visible reply. Pass one self-contained natural-language candidate preserving the user's explicit memory intent. Do not ask the user to rephrase ordinary first-person facts, and do not rewrite them into display-name or third-person wording. Do not include secrets, private personal details, medical/legal/financial/sensitive facts, or another person's personal preference, opinion, habit, identity, relationship, workflow, or private life. Runtime context derives actor, scope, source, and subject ids; the memory agent decides canonical stored content and memory kind, then the plugin derives storage target from kind.",
2442
2452
  executionMode: "sequential",
2443
2453
  inputSchema: createMemoryInputSchema2,
@@ -2510,6 +2520,12 @@ function createMemoryCreateTool(context) {
2510
2520
  }
2511
2521
  function createMemoryRemoveTool(context) {
2512
2522
  return definePluginTool({
2523
+ annotations: {
2524
+ destructiveHint: true,
2525
+ idempotentHint: true,
2526
+ openWorldHint: false,
2527
+ readOnlyHint: false
2528
+ },
2513
2529
  description: "Forget one memory visible in the active context. Use only ids or short id prefixes returned by listMemories or searchMemories. Never remove memories by hidden actor, Slack, scope, or subject identifiers.",
2514
2530
  executionMode: "sequential",
2515
2531
  inputSchema: removeMemoryInputSchema,
@@ -2535,7 +2551,12 @@ function createMemoryRemoveTool(context) {
2535
2551
  function createMemoryListTool(context) {
2536
2552
  return definePluginTool({
2537
2553
  description: "List active memories visible in the current context. Use when the user asks what Junior remembers or when memory ids are needed before removing a memory.",
2538
- annotations: { readOnlyHint: true, destructiveHint: false },
2554
+ annotations: {
2555
+ destructiveHint: false,
2556
+ idempotentHint: true,
2557
+ openWorldHint: false,
2558
+ readOnlyHint: true
2559
+ },
2539
2560
  inputSchema: listMemoriesInputSchema2,
2540
2561
  outputSchema: memoryManyOutputSchema,
2541
2562
  execute: async (input) => {
@@ -2552,7 +2573,12 @@ function createMemoryListTool(context) {
2552
2573
  function createMemorySearchTool(context) {
2553
2574
  return definePluginTool({
2554
2575
  description: "Search active memories visible in the current context. Use when the model needs targeted memory recall. The tool searches only the current actor and active conversation scopes.",
2555
- annotations: { readOnlyHint: true, destructiveHint: false },
2576
+ annotations: {
2577
+ destructiveHint: false,
2578
+ idempotentHint: true,
2579
+ openWorldHint: false,
2580
+ readOnlyHint: true
2581
+ },
2556
2582
  inputSchema: searchMemoriesInputSchema2,
2557
2583
  outputSchema: memoryManyOutputSchema,
2558
2584
  execute: async (input) => {
@@ -2577,7 +2603,54 @@ import {
2577
2603
  getSourceKey as getSourceKey2,
2578
2604
  isPrivateSource as isPrivateSource2
2579
2605
  } from "@sentry/junior-plugin-api";
2606
+ import { z as z9 } from "zod";
2607
+
2608
+ // src/events.ts
2609
+ import { defineConversationEvent } from "@sentry/junior-plugin-api";
2580
2610
  import { z as z8 } from "zod";
2611
+ var MAX_EVENT_PREVIEW_CHARS = 500;
2612
+ var capturedMemorySchema = z8.object({
2613
+ content: z8.string().min(1),
2614
+ id: z8.string().min(1),
2615
+ kind: z8.enum(MEMORY_KINDS),
2616
+ observedAtMs: z8.number().finite(),
2617
+ scope: z8.enum(MEMORY_SCOPES)
2618
+ }).strict();
2619
+ function eventPreview(content) {
2620
+ const trimmed = content.trim();
2621
+ if (trimmed.length <= MAX_EVENT_PREVIEW_CHARS) return trimmed;
2622
+ return `${trimmed.slice(0, MAX_EVENT_PREVIEW_CHARS - 3).trimEnd()}...`;
2623
+ }
2624
+ var memoriesCapturedEvent = defineConversationEvent({
2625
+ name: "memories_captured",
2626
+ version: 1,
2627
+ schema: z8.object({
2628
+ memories: z8.array(capturedMemorySchema).min(1).max(100)
2629
+ }).strict(),
2630
+ renderEvent(event) {
2631
+ const count = event.memories.length;
2632
+ return {
2633
+ icon: "brain",
2634
+ title: count === 1 ? "Memory captured" : "Memories captured",
2635
+ preview: count === 1 ? eventPreview(event.memories[0].content) : `${count} memories`,
2636
+ details: event.memories.map((memory) => ({
2637
+ title: memory.content,
2638
+ metadata: [memory.kind, memory.scope]
2639
+ }))
2640
+ };
2641
+ }
2642
+ });
2643
+ function capturedMemory(memory) {
2644
+ return {
2645
+ content: memory.content,
2646
+ id: memory.id,
2647
+ kind: memory.kind,
2648
+ observedAtMs: memory.observedAtMs,
2649
+ scope: memory.scope
2650
+ };
2651
+ }
2652
+
2653
+ // src/process-session.ts
2581
2654
  var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
2582
2655
  "createMemory",
2583
2656
  "listMemories",
@@ -2585,14 +2658,25 @@ var MEMORY_TOOL_NAMES = /* @__PURE__ */ new Set([
2585
2658
  "searchMemories"
2586
2659
  ]);
2587
2660
  var MEMORY_TASK_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2588
- var extractedMemoryCacheSchema = z8.array(
2589
- z8.object({
2590
- content: z8.string().min(1),
2591
- expiresAtMs: z8.number().finite().nullable(),
2592
- kind: z8.enum(MEMORY_KINDS),
2593
- evidenceMessageIndices: z8.array(z8.number().int().nonnegative()).min(1).max(10)
2661
+ var extractedMemoryCacheSchema = z9.array(
2662
+ z9.object({
2663
+ content: z9.string().min(1),
2664
+ expiresAtMs: z9.number().finite().nullable(),
2665
+ kind: z9.enum(MEMORY_KINDS),
2666
+ evidenceMessageIndices: z9.array(z9.number().int().nonnegative()).min(1).max(10)
2594
2667
  }).strict().transform(parseExtractedMemory)
2595
2668
  );
2669
+ function recordCapturedMemory(captured, result) {
2670
+ const supersededIds = new Set(result.supersededIds ?? []);
2671
+ for (let index2 = captured.length - 1; index2 >= 0; index2 -= 1) {
2672
+ if (supersededIds.has(captured[index2].id)) {
2673
+ captured.splice(index2, 1);
2674
+ }
2675
+ }
2676
+ if (result.created || result.idempotent) {
2677
+ captured.push(capturedMemory(result.memory));
2678
+ }
2679
+ }
2596
2680
  function isRunActorInstruction(entry) {
2597
2681
  return entry.type === "message" && entry.role === "user" && entry.provenance?.authority === "instruction" && entry.isRunActor === true;
2598
2682
  }
@@ -2711,6 +2795,7 @@ async function processMemorySession(context) {
2711
2795
  if (memories.length === 0) {
2712
2796
  return;
2713
2797
  }
2798
+ const captured = [];
2714
2799
  for (const memory of memories) {
2715
2800
  const target = routeExtractedMemory(memory, transcript, run);
2716
2801
  if (target === "drop") {
@@ -2718,10 +2803,15 @@ async function processMemorySession(context) {
2718
2803
  }
2719
2804
  const input = passiveInput(run.runId, memory, sourceKey2, target);
2720
2805
  if (target === "conversation") {
2721
- await store.createConversationMemory(input);
2806
+ const result2 = await store.createConversationMemory(input);
2807
+ recordCapturedMemory(captured, result2);
2722
2808
  continue;
2723
2809
  }
2724
- await store.createMemory(input);
2810
+ const result = await store.createMemory(input);
2811
+ recordCapturedMemory(captured, result);
2812
+ }
2813
+ if (captured.length > 0) {
2814
+ await context.events.emit(memoriesCapturedEvent({ memories: captured }));
2725
2815
  }
2726
2816
  }
2727
2817
 
@@ -2729,7 +2819,7 @@ async function processMemorySession(context) {
2729
2819
  import {
2730
2820
  definePromptContext
2731
2821
  } from "@sentry/junior-plugin-api";
2732
- import { z as z9 } from "zod";
2822
+ import { z as z10 } from "zod";
2733
2823
  var DEFAULT_RECALL_LIMIT = 5;
2734
2824
  var RECALL_CANDIDATE_LIMIT = 20;
2735
2825
  var MAX_PROMPT_CHARS = 4e3;
@@ -2744,15 +2834,15 @@ function trimContent(content, maxLength) {
2744
2834
  function formatObservedDate(observedAtMs) {
2745
2835
  return new Date(observedAtMs).toISOString().slice(0, 10);
2746
2836
  }
2747
- var recalledMemorySchema = z9.object({
2748
- id: z9.string().min(1),
2749
- content: z9.string().min(1).max(MAX_MEMORY_LINE_CHARS),
2750
- observedAtMs: z9.number().finite(),
2751
- scope: z9.enum(["personal", "conversation"]),
2752
- kind: z9.enum(["preference", "procedure", "knowledge"])
2837
+ var recalledMemorySchema = z10.object({
2838
+ id: z10.string().min(1),
2839
+ content: z10.string().min(1).max(MAX_MEMORY_LINE_CHARS),
2840
+ observedAtMs: z10.number().finite(),
2841
+ scope: z10.enum(["personal", "conversation"]),
2842
+ kind: z10.enum(["preference", "procedure", "knowledge"])
2753
2843
  }).strict();
2754
- var memoryRecallContextSchema = z9.object({
2755
- memories: z9.array(recalledMemorySchema).min(1).max(DEFAULT_RECALL_LIMIT)
2844
+ var memoryRecallContextSchema = z10.object({
2845
+ memories: z10.array(recalledMemorySchema).min(1).max(DEFAULT_RECALL_LIMIT)
2756
2846
  }).strict();
2757
2847
  function selectPromptMemories(memories) {
2758
2848
  const header = "Relevant memories for this request:";
@@ -2834,13 +2924,13 @@ async function createMemoryPromptContributions(context) {
2834
2924
 
2835
2925
  // src/operational-report.ts
2836
2926
  import { and as and4, eq as eq5, gt as gt4, isNull as isNull3, or as or4, sql as sql4 } from "drizzle-orm";
2837
- import { z as z10 } from "zod";
2927
+ import { z as z11 } from "zod";
2838
2928
  var DAY_MS = 24 * 60 * 60 * 1e3;
2839
2929
  var WINDOWS = [7, 30, 90];
2840
- var memoryDaySchema = z10.object({
2841
- conversation: z10.number().int().nonnegative(),
2842
- date: z10.string().date(),
2843
- personal: z10.number().int().nonnegative()
2930
+ var memoryDaySchema = z11.object({
2931
+ conversation: z11.number().int().nonnegative(),
2932
+ date: z11.string().date(),
2933
+ personal: z11.number().int().nonnegative()
2844
2934
  }).strict();
2845
2935
  function queryRows(result) {
2846
2936
  if (typeof result !== "object" || result === null || !("rows" in result) || !Array.isArray(result.rows)) {
@@ -2893,7 +2983,7 @@ async function aggregateMemoryDays(args) {
2893
2983
  LEFT JOIN daily ON daily.day = days.day
2894
2984
  ORDER BY days.day
2895
2985
  `);
2896
- return z10.array(memoryDaySchema).parse(queryRows(result));
2986
+ return z11.array(memoryDaySchema).parse(queryRows(result));
2897
2987
  }
2898
2988
  function formatCount(value) {
2899
2989
  return new Intl.NumberFormat("en-US").format(value);
@@ -3092,6 +3182,7 @@ function memoryPlugin(options = {}) {
3092
3182
  },
3093
3183
  model: modelId ? { structuredModelId: modelId } : { structuredModel: "default" },
3094
3184
  packageName: "@sentry/junior-memory",
3185
+ conversationEvents: [memoriesCapturedEvent],
3095
3186
  cli: {
3096
3187
  commands: [createMemoryCliCommand()]
3097
3188
  },