@sentry/junior-memory 0.124.0 → 0.124.2

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/api.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Authenticated REST resources for personal memories.
2
+ * Authenticated REST resources for viewer-visible memories.
3
3
  *
4
4
  * HTTP identity is one verified viewer. Linked platform actors are resolved
5
- * behind the route boundary and used only to authorize personal scopes.
5
+ * behind the route boundary and used only to authorize personal and public
6
+ * workspace scopes.
6
7
  */
7
8
  import { z } from "zod";
8
9
  import { type PluginRouteApp, type PluginUserPageActor } from "@sentry/junior-plugin-api";
@@ -18,6 +19,10 @@ export declare const memoryApiSchema: z.ZodObject<{
18
19
  knowledge: "knowledge";
19
20
  }>;
20
21
  observedAt: z.ZodISODateTime;
22
+ visibility: z.ZodEnum<{
23
+ public: "public";
24
+ private: "private";
25
+ }>;
21
26
  }, z.core.$strict>;
22
27
  export declare const memoryListResponseSchema: z.ZodObject<{
23
28
  memories: z.ZodArray<z.ZodObject<{
@@ -31,15 +36,40 @@ export declare const memoryListResponseSchema: z.ZodObject<{
31
36
  knowledge: "knowledge";
32
37
  }>;
33
38
  observedAt: z.ZodISODateTime;
39
+ visibility: z.ZodEnum<{
40
+ public: "public";
41
+ private: "private";
42
+ }>;
34
43
  }, z.core.$strict>>;
35
44
  nextCursor: z.ZodOptional<z.ZodString>;
36
45
  }, z.core.$strict>;
46
+ export declare const memoryDashboardResponseSchema: z.ZodObject<{
47
+ days: z.ZodArray<z.ZodObject<{
48
+ date: z.ZodISODate;
49
+ personal: z.ZodNumber;
50
+ public: z.ZodNumber;
51
+ }, z.core.$strict>>;
52
+ generatedAt: z.ZodISODateTime;
53
+ stats: z.ZodObject<{
54
+ active: z.ZodNumber;
55
+ automatic: z.ZodNumber;
56
+ createdThirtyDays: z.ZodNumber;
57
+ embedded: z.ZodNumber;
58
+ explicit: z.ZodNumber;
59
+ knowledge: z.ZodNumber;
60
+ personal: z.ZodNumber;
61
+ preference: z.ZodNumber;
62
+ procedure: z.ZodNumber;
63
+ public: z.ZodNumber;
64
+ }, z.core.$strict>;
65
+ }, z.core.$strict>;
37
66
  export type MemoryApi = z.output<typeof memoryApiSchema>;
67
+ export type MemoryDashboardResponse = z.output<typeof memoryDashboardResponseSchema>;
38
68
  export type MemoryListResponse = z.output<typeof memoryListResponseSchema>;
39
69
  interface MemoryApiOptions {
40
70
  actors(email: string): Promise<PluginUserPageActor[]>;
41
71
  db: MemoryDb;
42
72
  }
43
- /** Create the authenticated personal-memory REST app. */
73
+ /** Create the authenticated viewer-memory REST app. */
44
74
  export declare function createMemoryApi(options: MemoryApiOptions): PluginRouteApp;
45
75
  export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { memoryPlugin } from "./plugin";
2
- export { memoryApiSchema, memoryListResponseSchema, type MemoryApi, type MemoryListResponse, } from "./api";
2
+ export { memoryApiSchema, memoryDashboardResponseSchema, memoryListResponseSchema, type MemoryApi, type MemoryDashboardResponse, type MemoryListResponse, } from "./api";
3
3
  export type { MemoryPluginOptions } from "./plugin";
4
4
  export { createMemoryStore } from "./store";
5
5
  export type { ArchiveMemoryInput, CreateMemoryInput, CreateMemoryResult, ListMemoriesInput, MemoryDb, MemoryEmbeddingProvider, MemoryRecord, MemoryStore, MemoryStoreOptions, SearchMemoriesInput, } from "./store";
package/dist/index.js CHANGED
@@ -496,12 +496,12 @@ function visibleScopePredicate(scopes) {
496
496
  );
497
497
  }
498
498
  function activeVisiblePredicate(args) {
499
- const scopePredicate = visibleScopePredicate(args.scopes);
500
- if (!scopePredicate) {
499
+ const scopePredicate2 = visibleScopePredicate(args.scopes);
500
+ if (!scopePredicate2) {
501
501
  return void 0;
502
502
  }
503
503
  return and(
504
- scopePredicate,
504
+ scopePredicate2,
505
505
  isNull(juniorMemoryMemories.archivedAtMs),
506
506
  isNull(juniorMemoryMemories.supersededAtMs),
507
507
  isNull(juniorMemoryMemories.supersededById),
@@ -571,12 +571,12 @@ async function findByIdempotencyKey(args) {
571
571
  return void 0;
572
572
  }
573
573
  async function archiveExpiredMemoryBatch(args) {
574
- const scopePredicate = visibleScopePredicate(args.scopes);
575
- if (!scopePredicate) {
574
+ const scopePredicate2 = visibleScopePredicate(args.scopes);
575
+ if (!scopePredicate2) {
576
576
  return { archivedCount: 0 };
577
577
  }
578
578
  const predicates = [
579
- scopePredicate,
579
+ scopePredicate2,
580
580
  isNull(juniorMemoryMemories.archivedAtMs),
581
581
  isNull(juniorMemoryMemories.supersededAtMs),
582
582
  isNull(juniorMemoryMemories.supersededById),
@@ -1762,18 +1762,26 @@ import {
1762
1762
  import { z as z5 } from "zod";
1763
1763
 
1764
1764
  // src/personal-store.ts
1765
- import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2, ilike, lt, or as or2, sql as sql3 } from "drizzle-orm";
1765
+ import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2, ilike, like as like2, lt, or as or2, sql as sql3 } from "drizzle-orm";
1766
1766
  import { z as z4 } from "zod";
1767
1767
  var nonEmptyStringSchema3 = z4.string().min(1);
1768
+ var memoryVisibilitySchema = z4.enum(["private", "public"]);
1768
1769
  var personalMemoryCursorSchema = z4.object({
1769
1770
  createdAtMs: z4.number().finite(),
1770
1771
  id: nonEmptyStringSchema3
1771
1772
  }).strict();
1772
1773
  var personalMemoryPageInputSchema = z4.object({
1773
1774
  cursor: personalMemoryCursorSchema.optional(),
1775
+ kind: z4.enum(MEMORY_KINDS).optional(),
1774
1776
  limit: z4.number().int().min(1).max(50),
1775
- query: z4.string().max(200).optional()
1777
+ origin: z4.enum(["automatic", "explicit"]).optional(),
1778
+ query: z4.string().max(200).optional(),
1779
+ visibility: memoryVisibilitySchema.optional()
1776
1780
  }).strict();
1781
+ var personalMemoryTimelineInputSchema = z4.object({
1782
+ days: z4.number().int().min(1).max(365)
1783
+ }).strict();
1784
+ var DAY_MS = 24 * 60 * 60 * 1e3;
1777
1785
  var PersonalMemoryNotFoundError = class extends Error {
1778
1786
  constructor() {
1779
1787
  super("Memory was not found for the authenticated viewer.");
@@ -1790,6 +1798,39 @@ function personalScopes(runtimeContexts) {
1790
1798
  ).values()
1791
1799
  ];
1792
1800
  }
1801
+ function publicWorkspaceScopes(runtimeContexts) {
1802
+ return [
1803
+ ...new Map(
1804
+ runtimeContexts.flatMap((context) => {
1805
+ if (context.source.platform !== "slack" || !context.actor) {
1806
+ return [];
1807
+ }
1808
+ if (context.actor.platform !== "slack") {
1809
+ return [];
1810
+ }
1811
+ const scope = {
1812
+ scope: "conversation",
1813
+ scopeKey: `slack:${context.actor.teamId}`
1814
+ };
1815
+ return [[`${scope.scope}:${scope.scopeKey}`, scope]];
1816
+ })
1817
+ ).values()
1818
+ ];
1819
+ }
1820
+ function scopePredicate(scopes) {
1821
+ if (scopes.length === 0) return void 0;
1822
+ return or2(
1823
+ ...scopes.map(
1824
+ (scope) => and2(
1825
+ eq2(juniorMemoryMemories.scope, scope.scope),
1826
+ eq2(juniorMemoryMemories.scopeKey, scope.scopeKey)
1827
+ )
1828
+ )
1829
+ );
1830
+ }
1831
+ function utcDate(ms) {
1832
+ return new Date(ms).toISOString().slice(0, 10);
1833
+ }
1793
1834
  function searchTerms(query) {
1794
1835
  return [
1795
1836
  ...new Set(
@@ -1797,14 +1838,55 @@ function searchTerms(query) {
1797
1838
  )
1798
1839
  ];
1799
1840
  }
1841
+ function memoryOrigin(idempotencyKey) {
1842
+ if (idempotencyKey?.startsWith("session:")) return "automatic";
1843
+ if (idempotencyKey?.startsWith("tool:")) return "explicit";
1844
+ return "other";
1845
+ }
1846
+ function memoryVisibility(scope) {
1847
+ return scope === "personal" ? "private" : "public";
1848
+ }
1849
+ function personalMemoryRecord(row) {
1850
+ const memory = parseMemoryRow(row);
1851
+ return {
1852
+ ...memory,
1853
+ origin: memoryOrigin(row.idempotencyKey),
1854
+ sourcePlatform: row.sourcePlatform,
1855
+ visibility: memoryVisibility(memory.scope)
1856
+ };
1857
+ }
1858
+ function emptyStats() {
1859
+ return {
1860
+ active: 0,
1861
+ automatic: 0,
1862
+ createdThirtyDays: 0,
1863
+ embedded: 0,
1864
+ explicit: 0,
1865
+ knowledge: 0,
1866
+ personal: 0,
1867
+ preference: 0,
1868
+ procedure: 0,
1869
+ public: 0
1870
+ };
1871
+ }
1800
1872
  function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1801
- const scopes = personalScopes(runtimeContexts);
1873
+ const privateScopes = personalScopes(runtimeContexts);
1874
+ const publicScopes = publicWorkspaceScopes(runtimeContexts);
1875
+ const allScopes = [...privateScopes, ...publicScopes];
1802
1876
  const getNowMs = () => options.now?.() ?? Date.now();
1877
+ function scopesForVisibility(visibility) {
1878
+ if (visibility === "private") return privateScopes;
1879
+ if (visibility === "public") return publicScopes;
1880
+ return allScopes;
1881
+ }
1803
1882
  return {
1804
1883
  async archive(id) {
1805
1884
  const memoryId = nonEmptyStringSchema3.parse(id);
1806
1885
  const nowMs = getNowMs();
1807
- const predicate = activeVisiblePredicate({ nowMs, scopes });
1886
+ const predicate = activeVisiblePredicate({
1887
+ nowMs,
1888
+ scopes: privateScopes
1889
+ });
1808
1890
  if (!predicate) {
1809
1891
  throw new PersonalMemoryNotFoundError();
1810
1892
  }
@@ -1821,7 +1903,7 @@ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1821
1903
  async get(id) {
1822
1904
  const memoryId = nonEmptyStringSchema3.parse(id);
1823
1905
  const nowMs = getNowMs();
1824
- const predicate = activeVisiblePredicate({ nowMs, scopes });
1906
+ const predicate = activeVisiblePredicate({ nowMs, scopes: allScopes });
1825
1907
  if (!predicate) {
1826
1908
  throw new PersonalMemoryNotFoundError();
1827
1909
  }
@@ -1829,11 +1911,12 @@ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1829
1911
  if (!rows[0]) {
1830
1912
  throw new PersonalMemoryNotFoundError();
1831
1913
  }
1832
- return parseMemoryRow(rows[0]);
1914
+ return personalMemoryRecord(rows[0]);
1833
1915
  },
1834
1916
  async list(input) {
1835
1917
  input = personalMemoryPageInputSchema.parse(input);
1836
1918
  const nowMs = getNowMs();
1919
+ const scopes = scopesForVisibility(input.visibility);
1837
1920
  await archiveExpiredMemoryBatch({ db, nowMs, scopes });
1838
1921
  const active = activeVisiblePredicate({ nowMs, scopes });
1839
1922
  if (!active) {
@@ -1852,12 +1935,14 @@ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1852
1935
  (term) => ilike(juniorMemoryMemories.content, `%${term}%`)
1853
1936
  )
1854
1937
  );
1855
- const rows = await db.select().from(juniorMemoryMemories).where(and2(active, cursor, search)).orderBy(
1938
+ const kind = input.kind ? eq2(juniorMemoryMemories.kind, input.kind) : void 0;
1939
+ const origin = input.origin === "automatic" ? like2(juniorMemoryMemories.idempotencyKey, "session:%") : input.origin === "explicit" ? like2(juniorMemoryMemories.idempotencyKey, "tool:%") : void 0;
1940
+ const rows = await db.select().from(juniorMemoryMemories).where(and2(active, cursor, search, kind, origin)).orderBy(
1856
1941
  desc2(juniorMemoryMemories.createdAtMs),
1857
1942
  asc2(juniorMemoryMemories.id)
1858
1943
  ).limit(input.limit + 1);
1859
1944
  const hasNextPage = rows.length > input.limit;
1860
- const memories = rows.slice(0, input.limit).map(parseMemoryRow);
1945
+ const memories = rows.slice(0, input.limit).map(personalMemoryRecord);
1861
1946
  const last = memories.at(-1);
1862
1947
  return {
1863
1948
  memories,
@@ -1868,6 +1953,97 @@ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1868
1953
  }
1869
1954
  } : {}
1870
1955
  };
1956
+ },
1957
+ async stats() {
1958
+ const nowMs = getNowMs();
1959
+ await archiveExpiredMemoryBatch({ db, nowMs, scopes: allScopes });
1960
+ const active = activeVisiblePredicate({ nowMs, scopes: allScopes });
1961
+ if (!active) {
1962
+ return emptyStats();
1963
+ }
1964
+ const [counts] = await db.select({
1965
+ active: sql3`count(*)`.mapWith(Number),
1966
+ automatic: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'session:%')`.mapWith(
1967
+ Number
1968
+ ),
1969
+ createdThirtyDays: sql3`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${nowMs - 30 * 24 * 60 * 60 * 1e3})`.mapWith(
1970
+ Number
1971
+ ),
1972
+ embedded: sql3`count(${juniorMemoryEmbeddings.memoryId})`.mapWith(
1973
+ Number
1974
+ ),
1975
+ explicit: sql3`count(*) filter (where ${juniorMemoryMemories.idempotencyKey} like 'tool:%')`.mapWith(
1976
+ Number
1977
+ ),
1978
+ knowledge: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'knowledge')`.mapWith(
1979
+ Number
1980
+ ),
1981
+ personal: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
1982
+ Number
1983
+ ),
1984
+ preference: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'preference')`.mapWith(
1985
+ Number
1986
+ ),
1987
+ procedure: sql3`count(*) filter (where ${juniorMemoryMemories.kind} = 'procedure')`.mapWith(
1988
+ Number
1989
+ ),
1990
+ public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
1991
+ Number
1992
+ )
1993
+ }).from(juniorMemoryMemories).leftJoin(
1994
+ juniorMemoryEmbeddings,
1995
+ eq2(juniorMemoryEmbeddings.memoryId, juniorMemoryMemories.id)
1996
+ ).where(active);
1997
+ return {
1998
+ active: counts?.active ?? 0,
1999
+ automatic: counts?.automatic ?? 0,
2000
+ createdThirtyDays: counts?.createdThirtyDays ?? 0,
2001
+ embedded: counts?.embedded ?? 0,
2002
+ explicit: counts?.explicit ?? 0,
2003
+ knowledge: counts?.knowledge ?? 0,
2004
+ personal: counts?.personal ?? 0,
2005
+ preference: counts?.preference ?? 0,
2006
+ procedure: counts?.procedure ?? 0,
2007
+ public: counts?.public ?? 0
2008
+ };
2009
+ },
2010
+ async timeline(input) {
2011
+ input = personalMemoryTimelineInputSchema.parse(input);
2012
+ const todayMs = Date.parse(`${utcDate(getNowMs())}T00:00:00.000Z`);
2013
+ const startMs = todayMs - (input.days - 1) * DAY_MS;
2014
+ const ownership = scopePredicate(allScopes);
2015
+ if (!ownership) {
2016
+ return Array.from({ length: input.days }, (_, index2) => ({
2017
+ date: utcDate(startMs + index2 * DAY_MS),
2018
+ personal: 0,
2019
+ public: 0
2020
+ }));
2021
+ }
2022
+ const rows = await db.select({
2023
+ date: sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`.as(
2024
+ "date"
2025
+ ),
2026
+ personal: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'personal')`.mapWith(
2027
+ Number
2028
+ ),
2029
+ public: sql3`count(*) filter (where ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
2030
+ Number
2031
+ )
2032
+ }).from(juniorMemoryMemories).where(
2033
+ and2(ownership, gt2(juniorMemoryMemories.createdAtMs, startMs - 1))
2034
+ ).groupBy(
2035
+ sql3`to_char(to_timestamp(${juniorMemoryMemories.createdAtMs} / 1000.0) AT TIME ZONE 'UTC', 'YYYY-MM-DD')`
2036
+ );
2037
+ const byDate = new Map(rows.map((row) => [row.date, row]));
2038
+ return Array.from({ length: input.days }, (_, index2) => {
2039
+ const date = utcDate(startMs + index2 * DAY_MS);
2040
+ const row = byDate.get(date);
2041
+ return {
2042
+ date,
2043
+ personal: row?.personal ?? 0,
2044
+ public: row?.public ?? 0
2045
+ };
2046
+ });
1871
2047
  }
1872
2048
  };
1873
2049
  }
@@ -1876,8 +2052,11 @@ function createPersonalMemoryCollection(db, runtimeContexts, options = {}) {
1876
2052
  var cursorSchema = z5.object({
1877
2053
  createdAtMs: z5.number().finite(),
1878
2054
  id: z5.string().min(1),
2055
+ kind: z5.enum(["preference", "procedure", "knowledge"]).optional(),
2056
+ origin: z5.enum(["automatic", "explicit"]).optional(),
1879
2057
  query: z5.string().max(200).optional(),
1880
- version: z5.literal(1)
2058
+ version: z5.literal(1),
2059
+ visibility: z5.enum(["private", "public"]).optional()
1881
2060
  }).strict();
1882
2061
  var InvalidMemoryCursorError = class extends Error {
1883
2062
  constructor() {
@@ -1906,13 +2085,13 @@ function runtimeContext(actor) {
1906
2085
  }
1907
2086
  };
1908
2087
  }
1909
- function decodeCursor(value, query) {
2088
+ function decodeCursor(value, input) {
1910
2089
  if (!value) return void 0;
1911
2090
  try {
1912
2091
  const parsed = cursorSchema.parse(
1913
2092
  JSON.parse(Buffer.from(value, "base64url").toString("utf8"))
1914
2093
  );
1915
- if (parsed.query !== query) {
2094
+ if (parsed.query !== input.query || parsed.kind !== input.kind || parsed.origin !== input.origin || parsed.visibility !== input.visibility) {
1916
2095
  throw new InvalidMemoryCursorError();
1917
2096
  }
1918
2097
  return { createdAtMs: parsed.createdAtMs, id: parsed.id };
@@ -1920,9 +2099,16 @@ function decodeCursor(value, query) {
1920
2099
  throw new InvalidMemoryCursorError();
1921
2100
  }
1922
2101
  }
1923
- function encodeCursor(cursor, query) {
2102
+ function encodeCursor(cursor, input) {
1924
2103
  return Buffer.from(
1925
- JSON.stringify({ ...cursor, ...query ? { query } : {}, version: 1 }),
2104
+ JSON.stringify({
2105
+ ...cursor,
2106
+ ...input.query ? { query: input.query } : {},
2107
+ ...input.kind ? { kind: input.kind } : {},
2108
+ ...input.origin ? { origin: input.origin } : {},
2109
+ ...input.visibility ? { visibility: input.visibility } : {},
2110
+ version: 1
2111
+ }),
1926
2112
  "utf8"
1927
2113
  ).toString("base64url");
1928
2114
  }
@@ -1940,15 +2126,27 @@ function createViewerMemories(db, actors) {
1940
2126
  },
1941
2127
  async list(input) {
1942
2128
  const query = input.query?.trim() || void 0;
2129
+ const filters = {
2130
+ ...input.kind ? { kind: input.kind } : {},
2131
+ ...input.origin ? { origin: input.origin } : {},
2132
+ ...query ? { query } : {},
2133
+ ...input.visibility ? { visibility: input.visibility } : {}
2134
+ };
1943
2135
  const page = await collection.list({
1944
- cursor: decodeCursor(input.cursor, query),
1945
- limit: input.limit,
1946
- ...query ? { query } : {}
2136
+ cursor: decodeCursor(input.cursor, filters),
2137
+ ...filters,
2138
+ limit: input.limit
1947
2139
  });
1948
2140
  return {
1949
2141
  memories: page.memories,
1950
- ...page.nextCursor ? { nextCursor: encodeCursor(page.nextCursor, query) } : {}
2142
+ ...page.nextCursor ? { nextCursor: encodeCursor(page.nextCursor, filters) } : {}
1951
2143
  };
2144
+ },
2145
+ async stats() {
2146
+ return await collection.stats();
2147
+ },
2148
+ async timeline(input) {
2149
+ return await collection.timeline(input);
1952
2150
  }
1953
2151
  };
1954
2152
  }
@@ -1960,12 +2158,34 @@ var memoryApiSchema = z6.object({
1960
2158
  expiresAt: z6.iso.datetime().optional(),
1961
2159
  id: z6.string().min(1),
1962
2160
  kind: z6.enum(["preference", "procedure", "knowledge"]),
1963
- observedAt: z6.iso.datetime()
2161
+ observedAt: z6.iso.datetime(),
2162
+ visibility: z6.enum(["private", "public"])
1964
2163
  }).strict();
1965
2164
  var memoryListResponseSchema = z6.object({
1966
2165
  memories: z6.array(memoryApiSchema),
1967
2166
  nextCursor: z6.string().min(1).optional()
1968
2167
  }).strict();
2168
+ var memoryDashboardDaySchema = z6.object({
2169
+ date: z6.iso.date(),
2170
+ personal: z6.number().int().min(0),
2171
+ public: z6.number().int().min(0)
2172
+ }).strict();
2173
+ var memoryDashboardResponseSchema = z6.object({
2174
+ days: z6.array(memoryDashboardDaySchema).length(90),
2175
+ generatedAt: z6.iso.datetime(),
2176
+ stats: z6.object({
2177
+ active: z6.number().int().min(0),
2178
+ automatic: z6.number().int().min(0),
2179
+ createdThirtyDays: z6.number().int().min(0),
2180
+ embedded: z6.number().int().min(0),
2181
+ explicit: z6.number().int().min(0),
2182
+ knowledge: z6.number().int().min(0),
2183
+ personal: z6.number().int().min(0),
2184
+ preference: z6.number().int().min(0),
2185
+ procedure: z6.number().int().min(0),
2186
+ public: z6.number().int().min(0)
2187
+ }).strict()
2188
+ }).strict();
1969
2189
  var memoryListQuerySchema = z6.object({
1970
2190
  cursor: z6.string().min(1).max(1e3).optional(),
1971
2191
  limit: z6.coerce.number().int().min(1).max(50).default(25),
@@ -1984,7 +2204,8 @@ function apiMemory(memory) {
1984
2204
  ...memory.expiresAtMs !== void 0 ? { expiresAt: new Date(memory.expiresAtMs).toISOString() } : {},
1985
2205
  id: memory.id,
1986
2206
  kind: memory.kind,
1987
- observedAt: new Date(memory.observedAtMs).toISOString()
2207
+ observedAt: new Date(memory.observedAtMs).toISOString(),
2208
+ visibility: memory.visibility
1988
2209
  };
1989
2210
  }
1990
2211
  function viewerEmail(context) {
@@ -2004,12 +2225,28 @@ function createMemoryApi(options) {
2004
2225
  const url = new URL(request.url);
2005
2226
  const memoryPath = /^\/memories\/([^/]+)$/.exec(url.pathname);
2006
2227
  const isCollection = url.pathname === "/memories";
2007
- if (!isCollection && !memoryPath) {
2228
+ const isDashboard = url.pathname === "/dashboard";
2229
+ if (!isCollection && !isDashboard && !memoryPath) {
2008
2230
  return json({ error: "Not found." }, 404);
2009
2231
  }
2010
2232
  const actors = await options.actors(email);
2011
2233
  const memories = createViewerMemories(options.db, actors);
2012
2234
  try {
2235
+ if (isDashboard && (request.method === "GET" || request.method === "HEAD")) {
2236
+ const [stats, days] = await Promise.all([
2237
+ memories.stats(),
2238
+ memories.timeline({ days: 90 })
2239
+ ]);
2240
+ const body = memoryDashboardResponseSchema.parse({
2241
+ days,
2242
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2243
+ stats
2244
+ });
2245
+ return request.method === "HEAD" ? new Response(null, {
2246
+ headers: { "cache-control": "no-store" },
2247
+ status: 200
2248
+ }) : json(body);
2249
+ }
2013
2250
  if (isCollection && (request.method === "GET" || request.method === "HEAD")) {
2014
2251
  const query = memoryListQuerySchema.parse({
2015
2252
  cursor: url.searchParams.get("cursor") ?? void 0,
@@ -2925,7 +3162,7 @@ async function createMemoryPromptContributions(context) {
2925
3162
  // src/operational-report.ts
2926
3163
  import { and as and4, eq as eq5, gt as gt4, isNull as isNull3, or as or4, sql as sql4 } from "drizzle-orm";
2927
3164
  import { z as z11 } from "zod";
2928
- var DAY_MS = 24 * 60 * 60 * 1e3;
3165
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
2929
3166
  var WINDOWS = [7, 30, 90];
2930
3167
  var memoryDaySchema = z11.object({
2931
3168
  conversation: z11.number().int().nonnegative(),
@@ -2945,8 +3182,8 @@ function startOfUtcDay(value) {
2945
3182
  }
2946
3183
  async function aggregateMemoryDays(args) {
2947
3184
  const end = startOfUtcDay(args.nowMs);
2948
- const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1) - 1) * DAY_MS);
2949
- const endExclusiveMs = end.getTime() + DAY_MS;
3185
+ const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1) - 1) * DAY_MS2);
3186
+ const endExclusiveMs = end.getTime() + DAY_MS2;
2950
3187
  const table = juniorMemoryMemories;
2951
3188
  const result = await args.db.execute(sql4`
2952
3189
  WITH days AS (
@@ -3010,7 +3247,7 @@ async function buildMemoryOperationalReport(args) {
3010
3247
  conversation: sql4`count(*) filter (where ${active} and ${juniorMemoryMemories.scope} = 'conversation')`.mapWith(
3011
3248
  Number
3012
3249
  ),
3013
- createdThirtyDays: sql4`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${args.nowMs - 30 * DAY_MS})`.mapWith(
3250
+ createdThirtyDays: sql4`count(*) filter (where ${juniorMemoryMemories.createdAtMs} >= ${args.nowMs - 30 * DAY_MS2})`.mapWith(
3014
3251
  Number
3015
3252
  ),
3016
3253
  embedded: sql4`count(${juniorMemoryEmbeddings.memoryId}) filter (where ${active})`.mapWith(
@@ -3080,36 +3317,59 @@ async function buildMemoryOperationalReport(args) {
3080
3317
  }
3081
3318
 
3082
3319
  // src/user-pages.ts
3083
- function memoryKindLabel(kind) {
3084
- return kind.charAt(0).toUpperCase() + kind.slice(1);
3320
+ function titleCase(value) {
3321
+ return value.charAt(0).toUpperCase() + value.slice(1);
3085
3322
  }
3086
3323
  function rememberedDate(createdAtMs) {
3087
3324
  return new Intl.DateTimeFormat("en-US", {
3088
3325
  dateStyle: "medium",
3326
+ timeStyle: "short",
3089
3327
  timeZone: "UTC"
3090
3328
  }).format(new Date(createdAtMs));
3091
3329
  }
3330
+ function originLabel(origin) {
3331
+ if (origin === "automatic") return "Automatic";
3332
+ if (origin === "explicit") return "Explicit";
3333
+ return "Other";
3334
+ }
3335
+ function visibilityLabel(visibility) {
3336
+ return visibility === "public" ? "Public" : "Private";
3337
+ }
3338
+ function pageFilter(filter) {
3339
+ if (filter === "private") return { visibility: "private" };
3340
+ if (filter === "public") return { visibility: "public" };
3341
+ return {};
3342
+ }
3343
+ function pageEmptyText(input) {
3344
+ if (input.query) return "No memories matched your search.";
3345
+ if (input.filter === "private") return "No private memories yet.";
3346
+ if (input.filter === "public") return "No public memories yet.";
3347
+ return "No memories yet.";
3348
+ }
3092
3349
  function createMemoryUserPage() {
3093
3350
  return {
3094
3351
  id: "memories",
3095
3352
  label: "Memories",
3096
- description: "Personal facts and preferences Junior remembers about you.",
3353
+ navigation: "primary",
3354
+ description: "Personal and public memories Junior can use across conversations.",
3097
3355
  async read(ctx, input) {
3098
- const page = await createViewerMemories(
3356
+ const memories = createViewerMemories(
3099
3357
  ctx.db,
3100
3358
  ctx.viewer.actors
3101
- ).list({
3359
+ );
3360
+ const page = await memories.list({
3102
3361
  cursor: input.cursor,
3362
+ ...pageFilter(input.filter),
3103
3363
  limit: input.limit,
3104
3364
  ...input.query ? { query: input.query } : {}
3105
3365
  });
3106
3366
  return {
3107
3367
  type: "list",
3108
- emptyText: input.query ? "No memories matched your search." : "No personal memories yet.",
3368
+ emptyText: pageEmptyText(input),
3109
3369
  ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
3110
3370
  searchPlaceholder: "Search memories",
3111
3371
  records: page.memories.map((memory) => ({
3112
- actions: [
3372
+ actions: memory.visibility === "private" ? [
3113
3373
  {
3114
3374
  confirmation: "Forget this memory?",
3115
3375
  href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`,
@@ -3117,12 +3377,23 @@ function createMemoryUserPage() {
3117
3377
  method: "DELETE",
3118
3378
  tone: "danger"
3119
3379
  }
3120
- ],
3380
+ ] : [],
3121
3381
  id: memory.id,
3122
3382
  title: memory.content,
3123
3383
  metadata: [
3124
- { label: "Type", value: memoryKindLabel(memory.kind) },
3125
- { label: "Remembered", value: rememberedDate(memory.createdAtMs) }
3384
+ { label: "Type", value: titleCase(memory.kind) },
3385
+ { label: "Learned", value: originLabel(memory.origin) },
3386
+ { label: "Source", value: titleCase(memory.sourcePlatform) },
3387
+ {
3388
+ label: "Visibility",
3389
+ value: visibilityLabel(memory.visibility)
3390
+ },
3391
+ { label: "Remembered", value: rememberedDate(memory.createdAtMs) },
3392
+ { label: "Observed", value: rememberedDate(memory.observedAtMs) },
3393
+ {
3394
+ label: "Expires",
3395
+ value: memory.expiresAtMs ? rememberedDate(memory.expiresAtMs) : "Never"
3396
+ }
3126
3397
  ]
3127
3398
  }))
3128
3399
  };
@@ -3250,6 +3521,7 @@ export {
3250
3521
  MEMORY_KINDS,
3251
3522
  createMemoryStore,
3252
3523
  memoryApiSchema,
3524
+ memoryDashboardResponseSchema,
3253
3525
  memoryListResponseSchema,
3254
3526
  memoryPlugin
3255
3527
  };