@lobehub/lobehub 2.0.0-next.370 → 2.0.0-next.372

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +10 -0
  3. package/locales/zh-CN/memory.json +11 -0
  4. package/package.json +1 -1
  5. package/packages/builtin-tool-memory/package.json +1 -0
  6. package/packages/builtin-tool-memory/src/client/Inspector/SearchUserMemory/index.tsx +3 -2
  7. package/packages/builtin-tool-memory/src/manifest.ts +4 -1
  8. package/packages/context-engine/src/engine/messages/index.ts +1 -0
  9. package/packages/context-engine/src/engine/messages/types.ts +11 -0
  10. package/packages/database/src/models/userMemory/activity.ts +163 -0
  11. package/packages/database/src/models/userMemory/index.ts +1 -0
  12. package/packages/database/src/models/userMemory/model.ts +175 -1
  13. package/packages/database/src/repositories/userMemory/index.ts +5 -0
  14. package/packages/prompts/src/prompts/userMemory/__snapshots__/formatSearchResults.test.ts.snap +12 -0
  15. package/packages/prompts/src/prompts/userMemory/formatSearchResults.test.ts +41 -0
  16. package/packages/prompts/src/prompts/userMemory/formatSearchResults.ts +56 -2
  17. package/packages/types/src/userMemory/activity.ts +19 -0
  18. package/packages/types/src/userMemory/index.ts +1 -0
  19. package/packages/types/src/userMemory/tools.ts +10 -1
  20. package/src/app/(backend)/api/dev/memory-user-memory/benchmark-locomo/route.ts +19 -0
  21. package/src/app/[variants]/(main)/memory/_layout/Sidebar/Header/Nav.tsx +8 -0
  22. package/src/app/[variants]/(main)/memory/activities/features/ActivityDropdown.tsx +68 -0
  23. package/src/app/[variants]/(main)/memory/activities/features/ActivityRightPanel.tsx +113 -0
  24. package/src/app/[variants]/(main)/memory/activities/features/List/GridView/ActivityCard.tsx +36 -0
  25. package/src/app/[variants]/(main)/memory/activities/features/List/GridView/index.tsx +32 -0
  26. package/src/app/[variants]/(main)/memory/activities/features/List/TimelineView/ActivityCard.tsx +34 -0
  27. package/src/app/[variants]/(main)/memory/activities/features/List/TimelineView/index.tsx +41 -0
  28. package/src/app/[variants]/(main)/memory/activities/features/List/index.tsx +43 -0
  29. package/src/app/[variants]/(main)/memory/activities/index.tsx +125 -0
  30. package/src/app/[variants]/(main)/memory/features/EditableModal/index.tsx +1 -0
  31. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  32. package/src/locales/default/memory.ts +12 -0
  33. package/src/server/routers/lambda/userMemories.test.ts +18 -4
  34. package/src/server/routers/lambda/userMemories.ts +131 -1
  35. package/src/server/routers/lambda/userMemory.ts +34 -1
  36. package/src/server/services/memory/userMemory/extract.ts +1 -0
  37. package/src/services/chat/mecha/contextEngineering.test.ts +1 -0
  38. package/src/services/chat/mecha/memoryManager.ts +2 -0
  39. package/src/services/userMemory/crud.ts +17 -0
  40. package/src/services/userMemory/index.ts +18 -1
  41. package/src/store/userMemory/initialState.ts +4 -1
  42. package/src/store/userMemory/selectors.ts +1 -0
  43. package/src/store/userMemory/slices/activity/action.ts +123 -0
  44. package/src/store/userMemory/slices/activity/index.ts +2 -0
  45. package/src/store/userMemory/slices/activity/initialState.ts +23 -0
  46. package/src/store/userMemory/slices/agent/action.ts +1 -0
  47. package/src/store/userMemory/slices/base/action.ts +27 -4
  48. package/src/store/userMemory/store.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.372](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.371...v2.0.0-next.372)
6
+
7
+ <sup>Released on **2026-01-25**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **userMemories**: Memory activity list.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **userMemories**: Memory activity list, closes [#11785](https://github.com/lobehub/lobe-chat/issues/11785) ([a9f3a53](https://github.com/lobehub/lobe-chat/commit/a9f3a53))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.371](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.370...v2.0.0-next.371)
31
+
32
+ <sup>Released on **2026-01-25**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **builtin-tool-memory**: Missing activities for topK parameter.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **builtin-tool-memory**: Missing activities for topK parameter, closes [#11801](https://github.com/lobehub/lobe-chat/issues/11801) ([d6dee2a](https://github.com/lobehub/lobe-chat/commit/d6dee2a))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.370](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.369...v2.0.0-next.370)
6
56
 
7
57
  <sup>Released on **2026-01-25**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-25",
5
+ "version": "2.0.0-next.372"
6
+ },
7
+ {
8
+ "children": {},
9
+ "date": "2026-01-25",
10
+ "version": "2.0.0-next.371"
11
+ },
2
12
  {
3
13
  "children": {
4
14
  "features": [
@@ -1,4 +1,13 @@
1
1
  {
2
+ "activity.actions.delete": "删除",
3
+ "activity.actions.edit": "编辑",
4
+ "activity.defaultType": "事件",
5
+ "activity.deleteConfirm": "确认删除这条事件记忆?删除后将无法恢复",
6
+ "activity.deleteTitle": "删除事件记忆",
7
+ "activity.empty": "暂无事件记忆",
8
+ "activity.feedback": "反馈",
9
+ "activity.narrative": "叙述",
10
+ "activity.notes": "备注",
2
11
  "context.actions.delete": "删除",
3
12
  "context.actions.edit": "编辑",
4
13
  "context.defaultType": "上下文",
@@ -29,6 +38,7 @@
29
38
  "filter.sort.scoreImpact": "重要度",
30
39
  "filter.sort.scorePriority": "偏好权重",
31
40
  "filter.sort.scoreUrgency": "紧急度",
41
+ "filter.sort.startsAt": "开始时间",
32
42
  "identity.empty": "暂无身份记忆",
33
43
  "identity.filter.search": "搜索角色、关系或描述...",
34
44
  "identity.filter.type.all": "全部",
@@ -55,6 +65,7 @@
55
65
  "preference.empty": "暂无偏好记忆",
56
66
  "preference.source": "来源",
57
67
  "preference.suggestions": "助理可能会采取的行动",
68
+ "tab.activities": "活动",
58
69
  "tab.contexts": "情境",
59
70
  "tab.experiences": "经验",
60
71
  "tab.home": "首页",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.370",
3
+ "version": "2.0.0-next.372",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "devDependencies": {
20
20
  "@lobechat/types": "workspace:*",
21
+ "@types/json-schema": "^7.0.15",
21
22
  "promptfoo": "^0.120.17"
22
23
  },
23
24
  "peerDependencies": {
@@ -26,9 +26,10 @@ export const SearchUserMemoryInspector = memo<
26
26
  );
27
27
  }
28
28
 
29
- // pluginState is SearchMemoryResult directly (contexts, experiences, preferences)
29
+ // pluginState is SearchMemoryResult directly (activities, contexts, experiences, preferences)
30
30
  const resultCount = pluginState
31
- ? (pluginState.contexts?.length ?? 0) +
31
+ ? (pluginState.activities?.length ?? 0) +
32
+ (pluginState.contexts?.length ?? 0) +
32
33
  (pluginState.experiences?.length ?? 0) +
33
34
  (pluginState.preferences?.length ?? 0)
34
35
  : 0;
@@ -8,6 +8,7 @@ import {
8
8
  MERGE_STRATEGIES,
9
9
  RELATIONSHIPS,
10
10
  } from '@lobechat/types';
11
+ import { JSONSchema7 } from 'json-schema'
11
12
 
12
13
  import { systemPrompt } from './systemRole';
13
14
  import { MemoryApiName } from './types';
@@ -26,7 +27,9 @@ export const MemoryManifest: BuiltinToolManifest = {
26
27
  query: { type: 'string' },
27
28
  topK: {
28
29
  additionalProperties: false,
30
+ description: "Limits on number of memories to return per layer, default to search 3 activities, 0 contexts, 0 experiences, and 0 preferences if not specified.",
29
31
  properties: {
32
+ activities: { minimum: 0, type: 'integer' },
30
33
  contexts: { minimum: 0, type: 'integer' },
31
34
  experiences: { minimum: 0, type: 'integer' },
32
35
  preferences: { minimum: 0, type: 'integer' },
@@ -37,7 +40,7 @@ export const MemoryManifest: BuiltinToolManifest = {
37
40
  },
38
41
  required: ['query', 'topK'],
39
42
  type: 'object',
40
- },
43
+ } satisfies JSONSchema7,
41
44
  },
42
45
  {
43
46
  description:
@@ -18,6 +18,7 @@ export type {
18
18
  ToolsConfig,
19
19
  UIChatMessage,
20
20
  UserMemoryConfig,
21
+ UserMemoryActivityItem,
21
22
  UserMemoryContextItem,
22
23
  UserMemoryData,
23
24
  UserMemoryExperienceItem,
@@ -86,6 +86,16 @@ export interface UserMemoryPreferenceItem {
86
86
  [key: string]: unknown;
87
87
  }
88
88
 
89
+ export interface UserMemoryActivityItem {
90
+ endsAt?: string | Date | null;
91
+ id?: string;
92
+ startsAt?: string | Date | null;
93
+ status?: string | null;
94
+ timezone?: string | null;
95
+ type?: string | null;
96
+ [key: string]: unknown;
97
+ }
98
+
89
99
  export interface UserMemoryIdentityItem {
90
100
  description?: string | null;
91
101
  id?: string;
@@ -100,6 +110,7 @@ export interface UserMemoryIdentityItem {
100
110
  * Compatible with SearchMemoryResult from @lobechat/types
101
111
  */
102
112
  export interface UserMemoryData {
113
+ activities?: UserMemoryActivityItem[];
103
114
  contexts: UserMemoryContextItem[];
104
115
  experiences: UserMemoryExperienceItem[];
105
116
  identities?: UserMemoryIdentityItem[];
@@ -0,0 +1,163 @@
1
+ import type { ActivityListParams, ActivityListResult } from '@lobechat/types';
2
+ import { type SQL, and, asc, desc, eq, ilike, inArray, or, sql } from 'drizzle-orm';
3
+
4
+ import {
5
+ NewUserMemoryActivity,
6
+ UserMemoryActivity,
7
+ userMemories,
8
+ userMemoriesActivities,
9
+ } from '../../schemas';
10
+ import { LobeChatDatabase } from '../../type';
11
+
12
+ export class UserMemoryActivityModel {
13
+ private userId: string;
14
+ private db: LobeChatDatabase;
15
+
16
+ constructor(db: LobeChatDatabase, userId: string) {
17
+ this.userId = userId;
18
+ this.db = db;
19
+ }
20
+
21
+ create = async (params: Omit<NewUserMemoryActivity, 'userId'>) => {
22
+ const [result] = await this.db
23
+ .insert(userMemoriesActivities)
24
+ .values({ ...params, userId: this.userId })
25
+ .returning();
26
+
27
+ return result;
28
+ };
29
+
30
+ delete = async (id: string) => {
31
+ return this.db.transaction(async (tx) => {
32
+ const activity = await tx.query.userMemoriesActivities.findFirst({
33
+ where: and(eq(userMemoriesActivities.id, id), eq(userMemoriesActivities.userId, this.userId)),
34
+ });
35
+
36
+ if (!activity || !activity.userMemoryId) {
37
+ return { success: false };
38
+ }
39
+
40
+ await tx
41
+ .delete(userMemories)
42
+ .where(and(eq(userMemories.id, activity.userMemoryId), eq(userMemories.userId, this.userId)));
43
+
44
+ return { success: true };
45
+ });
46
+ };
47
+
48
+ deleteAll = async () => {
49
+ return this.db
50
+ .delete(userMemoriesActivities)
51
+ .where(eq(userMemoriesActivities.userId, this.userId));
52
+ };
53
+
54
+ query = async (limit = 50) => {
55
+ return this.db.query.userMemoriesActivities.findMany({
56
+ limit,
57
+ orderBy: [desc(userMemoriesActivities.createdAt)],
58
+ where: eq(userMemoriesActivities.userId, this.userId),
59
+ });
60
+ };
61
+
62
+ queryList = async (params: ActivityListParams = {}): Promise<ActivityListResult> => {
63
+ const { order = 'desc', page = 1, pageSize = 20, q, sort, status, tags, types } = params;
64
+
65
+ const normalizedPage = Math.max(1, page);
66
+ const normalizedPageSize = Math.min(Math.max(pageSize, 1), 100);
67
+ const offset = (normalizedPage - 1) * normalizedPageSize;
68
+ const normalizedQuery = typeof q === 'string' ? q.trim() : '';
69
+
70
+ const conditions: Array<SQL | undefined> = [
71
+ eq(userMemoriesActivities.userId, this.userId),
72
+ normalizedQuery
73
+ ? or(
74
+ ilike(userMemories.title, `%${normalizedQuery}%`),
75
+ ilike(userMemoriesActivities.narrative, `%${normalizedQuery}%`),
76
+ ilike(userMemoriesActivities.notes, `%${normalizedQuery}%`),
77
+ ilike(userMemoriesActivities.feedback, `%${normalizedQuery}%`),
78
+ )
79
+ : undefined,
80
+ types && types.length > 0 ? inArray(userMemoriesActivities.type, types) : undefined,
81
+ status && status.length > 0 ? inArray(userMemoriesActivities.status, status) : undefined,
82
+ tags && tags.length > 0
83
+ ? or(
84
+ ...tags.map(
85
+ (tag) =>
86
+ sql<boolean>`
87
+ COALESCE(${tag} = ANY(${userMemoriesActivities.tags}), false)
88
+ OR COALESCE(${tag} = ANY(${userMemories.tags}), false)
89
+ `,
90
+ ),
91
+ )
92
+ : undefined,
93
+ ];
94
+
95
+ const filters = conditions.filter((condition): condition is SQL => condition !== undefined);
96
+ const whereClause = filters.length > 0 ? and(...filters) : undefined;
97
+
98
+ const applyOrder = order === 'asc' ? asc : desc;
99
+ const sortColumn =
100
+ sort === 'startsAt' ? userMemoriesActivities.startsAt : userMemoriesActivities.capturedAt;
101
+
102
+ const orderByClauses = [
103
+ applyOrder(sortColumn),
104
+ applyOrder(userMemoriesActivities.updatedAt),
105
+ applyOrder(userMemoriesActivities.createdAt),
106
+ ];
107
+
108
+ const joinCondition = and(
109
+ eq(userMemories.id, userMemoriesActivities.userMemoryId),
110
+ eq(userMemories.userId, this.userId),
111
+ );
112
+
113
+ const [rows, totalResult] = await Promise.all([
114
+ this.db
115
+ .select({
116
+ capturedAt: userMemoriesActivities.capturedAt,
117
+ createdAt: userMemoriesActivities.createdAt,
118
+ endsAt: userMemoriesActivities.endsAt,
119
+ id: userMemoriesActivities.id,
120
+ narrative: userMemoriesActivities.narrative,
121
+ notes: userMemoriesActivities.notes,
122
+ startsAt: userMemoriesActivities.startsAt,
123
+ status: userMemoriesActivities.status,
124
+ tags: userMemoriesActivities.tags,
125
+ timezone: userMemoriesActivities.timezone,
126
+ title: userMemories.title,
127
+ type: userMemoriesActivities.type,
128
+ updatedAt: userMemoriesActivities.updatedAt,
129
+ })
130
+ .from(userMemoriesActivities)
131
+ .innerJoin(userMemories, joinCondition)
132
+ .where(whereClause)
133
+ .orderBy(...orderByClauses)
134
+ .limit(normalizedPageSize)
135
+ .offset(offset),
136
+ this.db
137
+ .select({ count: sql<number>`COUNT(*)::int` })
138
+ .from(userMemoriesActivities)
139
+ .innerJoin(userMemories, joinCondition)
140
+ .where(whereClause),
141
+ ]);
142
+
143
+ return {
144
+ items: rows,
145
+ page: normalizedPage,
146
+ pageSize: normalizedPageSize,
147
+ total: Number(totalResult[0]?.count ?? 0),
148
+ };
149
+ };
150
+
151
+ findById = async (id: string) => {
152
+ return this.db.query.userMemoriesActivities.findFirst({
153
+ where: and(eq(userMemoriesActivities.id, id), eq(userMemoriesActivities.userId, this.userId)),
154
+ });
155
+ };
156
+
157
+ update = async (id: string, value: Partial<UserMemoryActivity>) => {
158
+ return this.db
159
+ .update(userMemoriesActivities)
160
+ .set({ ...value, updatedAt: new Date() })
161
+ .where(and(eq(userMemoriesActivities.id, id), eq(userMemoriesActivities.userId, this.userId)));
162
+ };
163
+ }
@@ -1,3 +1,4 @@
1
+ export * from './activity';
1
2
  export * from './context';
2
3
  export * from './experience';
3
4
  export * from './identity';
@@ -331,7 +331,8 @@ export type QueryUserMemoriesSort =
331
331
  | 'scoreConfidence' // user_memories_experiences
332
332
  | 'scoreImpact' // user_memories_contexts
333
333
  | 'scorePriority' // user_memories_preferences
334
- | 'scoreUrgency'; // user_memories_contexts
334
+ | 'scoreUrgency' // user_memories_contexts
335
+ | 'startsAt'; // user_memories_activities
335
336
 
336
337
  export interface QueryUserMemoriesParams {
337
338
  categories?: string[];
@@ -341,6 +342,7 @@ export interface QueryUserMemoriesParams {
341
342
  pageSize?: number;
342
343
  q?: string;
343
344
  sort?: QueryUserMemoriesSort;
345
+ status?: string[];
344
346
  tags?: string[];
345
347
  types?: string[];
346
348
  }
@@ -850,6 +852,7 @@ export class UserMemoryModel {
850
852
  pageSize = 20,
851
853
  q,
852
854
  sort,
855
+ status,
853
856
  tags,
854
857
  types,
855
858
  } = params;
@@ -1005,6 +1008,98 @@ export class UserMemoryModel {
1005
1008
  total: Number(totalResult[0]?.count ?? 0),
1006
1009
  };
1007
1010
  }
1011
+ case LayersEnum.Activity: {
1012
+ const sortColumn =
1013
+ sort === 'startsAt'
1014
+ ? userMemoriesActivities.startsAt
1015
+ : userMemoriesActivities.capturedAt;
1016
+
1017
+ const orderByClauses = buildOrderBy(
1018
+ sortColumn,
1019
+ userMemoriesActivities.updatedAt,
1020
+ userMemoriesActivities.createdAt,
1021
+ );
1022
+ const joinCondition = and(
1023
+ eq(userMemories.id, userMemoriesActivities.userMemoryId),
1024
+ eq(userMemoriesActivities.userId, this.userId),
1025
+ );
1026
+
1027
+ const activityFilters: Array<SQL | undefined> = [
1028
+ whereClause,
1029
+ types && types.length > 0 ? inArray(userMemoriesActivities.type, types) : undefined,
1030
+ status && status.length > 0 ? inArray(userMemoriesActivities.status, status) : undefined,
1031
+ tags && tags.length > 0
1032
+ ? or(
1033
+ ...tags.map(
1034
+ (tag) =>
1035
+ sql<boolean>`
1036
+ COALESCE(${tag} = ANY(${userMemoriesActivities.tags}), false)
1037
+ OR COALESCE(${tag} = ANY(${userMemories.tags}), false)
1038
+ `,
1039
+ ),
1040
+ )
1041
+ : undefined,
1042
+ ];
1043
+ const activityWhereClause = activityFilters.some(
1044
+ (condition): condition is SQL => condition !== undefined,
1045
+ )
1046
+ ? and(
1047
+ ...(activityFilters.filter(
1048
+ (condition): condition is SQL => condition !== undefined,
1049
+ ) as SQL[]),
1050
+ )
1051
+ : undefined;
1052
+
1053
+ const [rows, totalResult] = await Promise.all([
1054
+ this.db
1055
+ .select({
1056
+ activity: {
1057
+ accessedAt: userMemoriesActivities.accessedAt,
1058
+ associatedLocations: userMemoriesActivities.associatedLocations,
1059
+ associatedObjects: userMemoriesActivities.associatedObjects,
1060
+ associatedSubjects: userMemoriesActivities.associatedSubjects,
1061
+ capturedAt: userMemoriesActivities.capturedAt,
1062
+ createdAt: userMemoriesActivities.createdAt,
1063
+ endsAt: userMemoriesActivities.endsAt,
1064
+ id: userMemoriesActivities.id,
1065
+ metadata: userMemoriesActivities.metadata,
1066
+ startsAt: userMemoriesActivities.startsAt,
1067
+ status: userMemoriesActivities.status,
1068
+ tags: userMemoriesActivities.tags,
1069
+ timezone: userMemoriesActivities.timezone,
1070
+ type: userMemoriesActivities.type,
1071
+ updatedAt: userMemoriesActivities.updatedAt,
1072
+ userId: userMemoriesActivities.userId,
1073
+ userMemoryId: userMemoriesActivities.userMemoryId,
1074
+ },
1075
+ memory: baseSelection,
1076
+ })
1077
+ .from(userMemories)
1078
+ .innerJoin(userMemoriesActivities, joinCondition)
1079
+ .where(activityWhereClause)
1080
+ .orderBy(...orderByClauses)
1081
+ .limit(normalizedPageSize)
1082
+ .offset(offset),
1083
+ this.db
1084
+ .select({ count: sql<number>`COUNT(*)::int` })
1085
+ .from(userMemories)
1086
+ .innerJoin(userMemoriesActivities, joinCondition)
1087
+ .where(activityWhereClause),
1088
+ ]);
1089
+
1090
+ return {
1091
+ items: rows.map((row) => {
1092
+ return {
1093
+ activity: row.activity,
1094
+ layer: LayersEnum.Activity,
1095
+ memory: row.memory,
1096
+ };
1097
+ }),
1098
+ page: normalizedPage,
1099
+ pageSize: normalizedPageSize,
1100
+ total: Number(totalResult[0]?.count ?? 0),
1101
+ };
1102
+ }
1008
1103
  case LayersEnum.Experience: {
1009
1104
  const scoreColumn =
1010
1105
  sort === 'scoreConfidence'
@@ -1310,6 +1405,58 @@ export class UserMemoryModel {
1310
1405
  sourceType,
1311
1406
  };
1312
1407
  }
1408
+ case LayersEnum.Activity: {
1409
+ const [activity] = await this.db
1410
+ .select({
1411
+ accessedAt: userMemoriesActivities.accessedAt,
1412
+ associatedLocations: userMemoriesActivities.associatedLocations,
1413
+ associatedObjects: userMemoriesActivities.associatedObjects,
1414
+ associatedSubjects: userMemoriesActivities.associatedSubjects,
1415
+ capturedAt: userMemoriesActivities.capturedAt,
1416
+ createdAt: userMemoriesActivities.createdAt,
1417
+ endsAt: userMemoriesActivities.endsAt,
1418
+ feedback: userMemoriesActivities.feedback,
1419
+ id: userMemoriesActivities.id,
1420
+ metadata: userMemoriesActivities.metadata,
1421
+ narrative: userMemoriesActivities.narrative,
1422
+ notes: userMemoriesActivities.notes,
1423
+ startsAt: userMemoriesActivities.startsAt,
1424
+ status: userMemoriesActivities.status,
1425
+ tags: userMemoriesActivities.tags,
1426
+ timezone: userMemoriesActivities.timezone,
1427
+ type: userMemoriesActivities.type,
1428
+ updatedAt: userMemoriesActivities.updatedAt,
1429
+ userId: userMemoriesActivities.userId,
1430
+ userMemoryId: userMemoriesActivities.userMemoryId,
1431
+ })
1432
+ .from(userMemoriesActivities)
1433
+ .where(
1434
+ and(eq(userMemoriesActivities.id, id), eq(userMemoriesActivities.userId, this.userId)),
1435
+ )
1436
+ .limit(1);
1437
+ if (!activity?.userMemoryId) {
1438
+ return undefined;
1439
+ }
1440
+
1441
+ const memory = await this.findUserMemoryRawById(activity.userMemoryId);
1442
+ if (!memory) {
1443
+ return undefined;
1444
+ }
1445
+ if (memory.memoryLayer !== LayersEnum.Activity) {
1446
+ return undefined;
1447
+ }
1448
+
1449
+ const { sourceId, sourceType } = await this.extractSourceMetadata(activity.metadata);
1450
+ const source = sourceId ? await this.topicModel.findById(sourceId) : undefined;
1451
+
1452
+ return {
1453
+ activity,
1454
+ layer,
1455
+ memory,
1456
+ source,
1457
+ sourceType,
1458
+ };
1459
+ }
1313
1460
  case LayersEnum.Experience: {
1314
1461
  const [experience] = await this.db
1315
1462
  .select({
@@ -1630,6 +1777,33 @@ export class UserMemoryModel {
1630
1777
  );
1631
1778
  };
1632
1779
 
1780
+ updateActivityVectors = async (
1781
+ id: string,
1782
+ vectors: { feedbackVector?: number[] | null; narrativeVector?: number[] | null },
1783
+ ): Promise<void> => {
1784
+ const vectorUpdates: Partial<typeof userMemoriesActivities.$inferInsert> = {};
1785
+ if (vectors.feedbackVector !== undefined) {
1786
+ vectorUpdates.feedbackVector = vectors.feedbackVector;
1787
+ }
1788
+ if (vectors.narrativeVector !== undefined) {
1789
+ vectorUpdates.narrativeVector = vectors.narrativeVector;
1790
+ }
1791
+
1792
+ if (Object.keys(vectorUpdates).length === 0) {
1793
+ return;
1794
+ }
1795
+
1796
+ await this.db
1797
+ .update(userMemoriesActivities)
1798
+ .set({
1799
+ ...vectorUpdates,
1800
+ updatedAt: new Date(),
1801
+ })
1802
+ .where(
1803
+ and(eq(userMemoriesActivities.id, id), eq(userMemoriesActivities.userId, this.userId)),
1804
+ );
1805
+ };
1806
+
1633
1807
  addIdentityEntry = async (params: AddIdentityEntryParams): Promise<AddIdentityEntryResult> => {
1634
1808
  const now = new Date();
1635
1809
 
@@ -1,4 +1,5 @@
1
1
  import {
2
+ UserMemoryActivitiesWithoutVectors,
2
3
  UserMemoryContextsWithoutVectors,
3
4
  UserMemoryExperiencesWithoutVectors,
4
5
  UserMemoryIdentitiesWithoutVectors,
@@ -25,3 +26,7 @@ export interface DisplayPreferenceMemory extends UserMemoryPreferencesWithoutVec
25
26
  export type DisplayContextMemory = UserMemoryContextsWithoutVectors;
26
27
 
27
28
  export type DisplayIdentityMemory = UserMemoryIdentitiesWithoutVectors;
29
+
30
+ export interface DisplayActivityMemory extends UserMemoryActivitiesWithoutVectors {
31
+ title: string | null;
32
+ }
@@ -1,5 +1,17 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`formatMemorySearchResults > should format activity memories with scheduling and feedback details 1`] = `
4
+ "<memories query="meeting" total="1">
5
+ <activities count="1">
6
+ <activity id="act-1" type="meeting" status="scheduled" startsAt="2024-01-01T10:00:00.000Z" endsAt="2024-01-01T11:00:00.000Z" timezone="UTC">
7
+ <feedback>Great alignment</feedback>
8
+ <narrative>Covered Q1 roadmap and risks</narrative>
9
+ <notes>Share slides after the call</notes>
10
+ </activity>
11
+ </activities>
12
+ </memories>"
13
+ `;
14
+
3
15
  exports[`formatMemorySearchResults > should format context memories with full content 1`] = `
4
16
  "<memories query="project" total="1">
5
17
  <contexts count="1">