@lobehub/lobehub 2.0.0-next.360 → 2.0.0-next.362

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 (76) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +2 -1
  3. package/changelog/v1.json +14 -0
  4. package/locales/en-US/chat.json +3 -1
  5. package/locales/zh-CN/chat.json +2 -0
  6. package/package.json +1 -1
  7. package/packages/const/src/userMemory.ts +1 -0
  8. package/packages/context-engine/src/base/BaseEveryUserContentProvider.ts +204 -0
  9. package/packages/context-engine/src/base/BaseLastUserContentProvider.ts +1 -8
  10. package/packages/context-engine/src/base/__tests__/BaseEveryUserContentProvider.test.ts +354 -0
  11. package/packages/context-engine/src/base/constants.ts +20 -0
  12. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +27 -23
  13. package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +364 -0
  14. package/packages/context-engine/src/providers/PageEditorContextInjector.ts +17 -13
  15. package/packages/context-engine/src/providers/PageSelectionsInjector.ts +65 -0
  16. package/packages/context-engine/src/providers/__tests__/PageSelectionsInjector.test.ts +333 -0
  17. package/packages/context-engine/src/providers/index.ts +3 -1
  18. package/packages/database/src/models/userMemory/model.ts +178 -3
  19. package/packages/database/src/models/userMemory/sources/benchmarkLoCoMo.ts +1 -1
  20. package/packages/memory-user-memory/package.json +2 -1
  21. package/packages/memory-user-memory/promptfoo/evals/activity/basic/buildMessages.ts +40 -0
  22. package/packages/memory-user-memory/promptfoo/evals/activity/basic/eval.yaml +13 -0
  23. package/packages/memory-user-memory/promptfoo/evals/activity/basic/prompt.ts +5 -0
  24. package/packages/memory-user-memory/promptfoo/evals/activity/basic/tests/cases.ts +106 -0
  25. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/buildMessages.ts +104 -0
  26. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/eval.yaml +13 -0
  27. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/prompt.ts +5 -0
  28. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/benchmark-locomo-payload-conv-26.json +149 -0
  29. package/packages/memory-user-memory/promptfoo/evals/activity/locomo/tests/cases.ts +72 -0
  30. package/packages/memory-user-memory/promptfoo/response-formats/activity.json +370 -0
  31. package/packages/memory-user-memory/promptfoo/response-formats/experience.json +14 -0
  32. package/packages/memory-user-memory/promptfoo/response-formats/identity.json +281 -255
  33. package/packages/memory-user-memory/promptfooconfig.yaml +1 -0
  34. package/packages/memory-user-memory/scripts/generate-response-formats.ts +26 -2
  35. package/packages/memory-user-memory/src/extractors/activity.ts +44 -0
  36. package/packages/memory-user-memory/src/extractors/gatekeeper.test.ts +2 -1
  37. package/packages/memory-user-memory/src/extractors/gatekeeper.ts +2 -1
  38. package/packages/memory-user-memory/src/extractors/index.ts +1 -0
  39. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +3 -3
  40. package/packages/memory-user-memory/src/prompts/index.ts +7 -1
  41. package/packages/memory-user-memory/src/prompts/layers/activity.ts +90 -0
  42. package/packages/memory-user-memory/src/prompts/layers/index.ts +1 -0
  43. package/packages/memory-user-memory/src/providers/existingUserMemory.test.ts +25 -1
  44. package/packages/memory-user-memory/src/providers/existingUserMemory.ts +113 -0
  45. package/packages/memory-user-memory/src/schemas/activity.ts +315 -0
  46. package/packages/memory-user-memory/src/schemas/experience.ts +5 -5
  47. package/packages/memory-user-memory/src/schemas/gatekeeper.ts +1 -0
  48. package/packages/memory-user-memory/src/schemas/index.ts +1 -0
  49. package/packages/memory-user-memory/src/services/extractExecutor.ts +29 -0
  50. package/packages/memory-user-memory/src/types.ts +7 -0
  51. package/packages/prompts/src/agents/index.ts +1 -0
  52. package/packages/prompts/src/agents/pageSelectionContext.ts +28 -0
  53. package/packages/types/src/aiChat.ts +4 -0
  54. package/packages/types/src/message/common/index.ts +1 -0
  55. package/packages/types/src/message/common/metadata.ts +8 -0
  56. package/packages/types/src/message/common/pageSelection.ts +36 -0
  57. package/packages/types/src/message/ui/params.ts +16 -0
  58. package/packages/types/src/serverConfig.ts +1 -1
  59. package/packages/types/src/userMemory/layers.ts +52 -0
  60. package/packages/types/src/userMemory/list.ts +20 -2
  61. package/packages/types/src/userMemory/shared.ts +22 -1
  62. package/packages/types/src/userMemory/trace.ts +1 -0
  63. package/packages/types/src/util.ts +9 -1
  64. package/scripts/prebuild.mts +1 -0
  65. package/src/features/ChatInput/Desktop/ContextContainer/ContextList.tsx +1 -1
  66. package/src/features/Conversation/ChatInput/index.tsx +9 -1
  67. package/src/features/Conversation/Messages/User/components/MessageContent.tsx +7 -1
  68. package/src/features/Conversation/Messages/User/components/PageSelections.tsx +62 -0
  69. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +5 -1
  70. package/src/libs/next/proxy/define-config.ts +1 -0
  71. package/src/locales/default/chat.ts +3 -2
  72. package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -1
  73. package/src/server/routers/lambda/aiChat.ts +7 -0
  74. package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +2 -0
  75. package/src/server/services/memory/userMemory/extract.ts +108 -7
  76. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +5 -19
@@ -0,0 +1,315 @@
1
+ import type { GenerateObjectSchema } from '@lobechat/model-runtime';
2
+ import { ActivityTypeEnum, LayersEnum, TypesEnum } from '@lobechat/types';
3
+ import { type JSONSchema7 } from 'json-schema';
4
+
5
+ export interface WithActivity {
6
+ associatedLocations?: {
7
+ address?: string;
8
+ extra?: string | null;
9
+ name?: string;
10
+ tags?: string[];
11
+ type?: string;
12
+ }[];
13
+ associatedObjects?: {
14
+ extra?: string | null;
15
+ name?: string;
16
+ type?: string;
17
+ }[];
18
+ associatedSubjects?: {
19
+ extra?: string | null;
20
+ name?: string;
21
+ type?: string;
22
+ }[];
23
+ endsAt?: string;
24
+ feedback?: string;
25
+ metadata?: Record<string, unknown>;
26
+ narrative: string;
27
+ notes?: string;
28
+ startsAt?: string;
29
+ status?: string;
30
+ tags?: string[];
31
+ timezone?: string;
32
+ type: ActivityTypeEnum | string;
33
+ }
34
+
35
+ export interface ActivityMemoryItem {
36
+ details: string;
37
+ memoryCategory: string;
38
+ memoryLayer: LayersEnum.Activity;
39
+ memoryType: TypesEnum.Activity;
40
+ summary: string;
41
+ tags: string[];
42
+ title: string;
43
+ withActivity: WithActivity;
44
+ }
45
+
46
+ export interface ActivityMemory {
47
+ memories: ActivityMemoryItem[];
48
+ }
49
+
50
+ export const ActivityMemorySchema: GenerateObjectSchema = {
51
+ description:
52
+ 'Extract episodic activities with clear timelines, participants, objects, subjects, locations, and feelings. Temporal and associated fields are optional—omit when missing rather than guessing.',
53
+ name: 'activity_extraction',
54
+ schema: {
55
+ additionalProperties: false,
56
+ properties: {
57
+ memories: {
58
+ description:
59
+ 'Array of extracted activity memories. Use an empty array when no activity should be captured.',
60
+ items: {
61
+ additionalProperties: false,
62
+ description:
63
+ 'Self-contained activity memory describing what happened, when, where, with whom, and how it felt.',
64
+ examples: [
65
+ {
66
+ details:
67
+ 'Talked through renewal scope, confirmed timeline flexibility, and captured follow-ups.',
68
+ memoryCategory: 'work',
69
+ memoryType: 'activity',
70
+ summary: 'Client Q2 renewal meeting with Alice (ACME)',
71
+ tags: ['meeting', 'client', 'renewal'],
72
+ title: 'ACME Q2 renewal meeting',
73
+ withActivity: {
74
+ associatedLocations: [
75
+ {
76
+ address: '123 Main St, New York, NY',
77
+ name: 'ACME HQ',
78
+ },
79
+ ],
80
+ associatedSubjects: [
81
+ { name: 'Alice Smith', type: 'person' },
82
+ ],
83
+ endsAt: '2024-05-03T15:00:00-04:00',
84
+ feedback: 'Positive momentum; Alice felt heard and open to renewal.',
85
+ narrative:
86
+ 'Alice and User reviewed Q2 renewal scope, aligned on reduced deliverables, and agreed to share revised pricing next week.',
87
+ notes: 'Agenda: renewal scope, pricing, next steps.',
88
+ startsAt: '2024-05-03T14:00:00-04:00',
89
+ status: 'completed',
90
+ timezone: 'America/New_York',
91
+ type: 'meeting',
92
+ },
93
+ },
94
+ {
95
+ details: 'Routine check-up; discussed migraines and sleep habits.',
96
+ memoryCategory: 'health',
97
+ memoryType: 'activity',
98
+ summary: 'Doctor appointment with Dr. Kim about migraines',
99
+ tags: ['appointment', 'health'],
100
+ title: 'Neurology follow-up',
101
+ withActivity: {
102
+ associatedLocations: [
103
+ {
104
+ name: 'City Neurology Clinic',
105
+ },
106
+ ],
107
+ associatedSubjects: [
108
+ { name: 'Dr. Kim', type: 'person' },
109
+ ],
110
+ feedback: 'Felt reassured; plan seems manageable.',
111
+ narrative:
112
+ 'User saw Dr. Kim to review migraine frequency; decided to track sleep, hydration, and start a low-dose preventive.',
113
+ notes: 'Discussed triggers, hydration, and medication side effects.',
114
+ status: 'completed',
115
+ type: 'appointment',
116
+ },
117
+ },
118
+ ],
119
+ properties: {
120
+ details: {
121
+ description:
122
+ 'Optional detailed information or longer notes supporting the summary and narrative.',
123
+ type: 'string',
124
+ },
125
+ memoryCategory: {
126
+ description:
127
+ 'Memory category best matching the activity (e.g., work, health, travel, relationships).',
128
+ type: 'string',
129
+ },
130
+ memoryType: {
131
+ const: TypesEnum.Activity,
132
+ description: 'Memory type; always activity.',
133
+ type: 'string',
134
+ },
135
+ summary: {
136
+ description: 'Concise overview of this activity.',
137
+ type: 'string',
138
+ },
139
+ tags: {
140
+ description: 'Model-generated tags summarizing key facets of the activity.',
141
+ items: { type: 'string' },
142
+ type: 'array',
143
+ },
144
+ title: {
145
+ description:
146
+ 'Brief descriptive title for the activity, e.g., "Dinner with friends at Marina".',
147
+ type: 'string',
148
+ },
149
+ withActivity: {
150
+ additionalProperties: false,
151
+ description:
152
+ 'Structured activity fields. Temporal and association values are optional—include only when the user mentioned them.',
153
+ properties: {
154
+ associatedLocations: {
155
+ description:
156
+ 'Places linked to this activity. Capture any mentioned venue, address, or setting.',
157
+ items: {
158
+ additionalProperties: false,
159
+ properties: {
160
+ address: {
161
+ description: 'Free-form address or directions if provided.',
162
+ type: ['string', 'null'],
163
+ },
164
+ extra: {
165
+ description: 'Optional key-value metadata related to the location.',
166
+ type: ['string', 'null'],
167
+ },
168
+ name: {
169
+ description: 'Place name or venue label.',
170
+ type: 'string',
171
+ },
172
+ tags: {
173
+ description: 'Place-related tags (e.g., indoor, outdoor, virtual).',
174
+ items: { type: 'string' },
175
+ type: ['array', 'null'],
176
+ },
177
+ type: {
178
+ description: 'Place type or category (office, clinic, restaurant, virtual).',
179
+ type: 'string',
180
+ },
181
+ },
182
+ required: ['type', 'name', 'address', 'tags', 'extra'],
183
+ type: 'object',
184
+ },
185
+ type: 'array',
186
+ },
187
+ associatedObjects: {
188
+ description:
189
+ 'Non-living entities or items tied to the activity (e.g., transportation for trips, devices, tools).',
190
+ items: {
191
+ additionalProperties: false,
192
+ properties: {
193
+ extra: {
194
+ description: 'Optional key-value metadata related to the object.',
195
+ type: ['string', 'null'],
196
+ },
197
+ name: {
198
+ description: 'Name or label of the object (e.g., “MacBook”, “flight UA123”).',
199
+ type: 'string',
200
+ },
201
+ type: {
202
+ description: 'Object category (e.g., transportation, device, document).',
203
+ enum: ['application', 'item', 'knowledge', 'other', 'person', 'place'],
204
+ type: 'string',
205
+ },
206
+ },
207
+ required: ['type', 'name', 'extra'],
208
+ type: 'object',
209
+ },
210
+ type: 'array',
211
+ },
212
+ associatedSubjects: {
213
+ description:
214
+ 'Living beings involved (people, pets, groups). Use when the subject lacks a known identity ID.',
215
+ items: {
216
+ additionalProperties: false,
217
+ properties: {
218
+ extra: {
219
+ description: 'Optional key-value metadata related to the subject.',
220
+ type: ['string', 'null'],
221
+ },
222
+ name: {
223
+ description: 'Name or short label of the subject.',
224
+ type: 'string',
225
+ },
226
+ type: {
227
+ description: 'Subject category (e.g., person, pet, group).',
228
+ enum: ['person', 'pet', 'group', 'other'],
229
+ type: 'string',
230
+ },
231
+ },
232
+ required: ['type', 'name', 'extra'],
233
+ type: 'object',
234
+ },
235
+ type: 'array',
236
+ },
237
+ endsAt: {
238
+ description:
239
+ 'ISO 8601 end time for the activity when specified. Omit if not explicitly provided.',
240
+ format: 'date-time',
241
+ type: ['string', 'null'],
242
+ },
243
+ feedback: {
244
+ description:
245
+ 'Subjective feelings or evaluation of how the activity went (mood, satisfaction, effort).',
246
+ type: ['string', 'null'],
247
+ },
248
+ metadata: {
249
+ additionalProperties: false,
250
+ description:
251
+ 'Additional structured metadata to keep raw hints (JSON object). Use sparingly.',
252
+ type: ['object', 'null'],
253
+ },
254
+ narrative: {
255
+ description:
256
+ 'Factual story of what happened (chronology, participants, outcomes). Required for recall.',
257
+ type: 'string',
258
+ },
259
+ notes: {
260
+ description:
261
+ 'Short annotations such as agenda, preparation, or quick bullets distinct from narrative.',
262
+ type: ['string', 'null'],
263
+ },
264
+ startsAt: {
265
+ description:
266
+ 'ISO 8601 start time for the activity when specified. Omit if not explicitly provided.',
267
+ format: 'date-time',
268
+ type: ['string', 'null'],
269
+ },
270
+ status: {
271
+ description:
272
+ 'Lifecycle status when mentioned. Use planned/completed/cancelled/ongoing/on_hold/pending. Omit if unclear.',
273
+ enum: ['planned', 'completed', 'cancelled', 'ongoing', 'on_hold', 'pending'],
274
+ type: ['string', 'null'],
275
+ },
276
+ tags: {
277
+ description: 'Optional activity-specific tags or facets.',
278
+ items: { type: 'string' },
279
+ type: ['array', 'null'],
280
+ },
281
+ timezone: {
282
+ description:
283
+ 'IANA timezone string for the start/end times when provided (e.g., "America/New_York").',
284
+ type: ['string', 'null'],
285
+ },
286
+ type: {
287
+ description:
288
+ 'Activity type enum. Choose the closest match; fall back to "other" when unclear.',
289
+ enum: Object.values(ActivityTypeEnum),
290
+ type: 'string',
291
+ },
292
+ },
293
+ required: ['type', 'narrative', 'feedback', 'notes', 'associatedLocations', 'associatedSubjects', 'associatedObjects', 'startsAt', 'endsAt', 'status', 'tags', 'timezone', 'metadata'],
294
+ type: 'object',
295
+ },
296
+ },
297
+ required: [
298
+ 'title',
299
+ 'summary',
300
+ 'details',
301
+ 'memoryType',
302
+ 'memoryCategory',
303
+ 'tags',
304
+ 'withActivity',
305
+ ],
306
+ type: 'object',
307
+ },
308
+ type: 'array',
309
+ },
310
+ } satisfies JSONSchema7['properties'],
311
+ required: ['memories'],
312
+ type: 'object',
313
+ },
314
+ strict: true,
315
+ };
@@ -8,6 +8,11 @@ import { MemoryTypeSchema } from './common';
8
8
  export const WithExperienceSchema = z.object({
9
9
  action: z.string().describe('Narrative describing actions taken or behaviors exhibited'),
10
10
  keyLearning: z.string().describe('Narrative describing key insights or lessons learned'),
11
+ knowledgeValueScore: z
12
+ .number()
13
+ .min(0)
14
+ .max(1)
15
+ .describe('Numeric score (0-1) describing how reusable and shareable this experience is'),
11
16
  labels: z.array(z.string()).describe('Model generated tags that summarize the experience facets'),
12
17
  possibleOutcome: z.string().describe('Narrative describing potential outcomes or learnings'),
13
18
  problemSolvingScore: z
@@ -15,11 +20,6 @@ export const WithExperienceSchema = z.object({
15
20
  .min(0)
16
21
  .max(1)
17
22
  .describe('Numeric score (0-1) describing how effectively the problem was solved'),
18
- knowledgeValueScore: z
19
- .number()
20
- .min(0)
21
- .max(1)
22
- .describe('Numeric score (0-1) describing how reusable and shareable this experience is'),
23
23
  reasoning: z.string().describe('Narrative describing the thought process or motivations'),
24
24
  scoreConfidence: z
25
25
  .number()
@@ -14,6 +14,7 @@ export type LayerDecision = z.infer<typeof LayerDecisionSchema>;
14
14
  * Gatekeeper result schema for memory layers
15
15
  */
16
16
  export const GatekeeperResultSchema = z.object({
17
+ activity: LayerDecisionSchema,
17
18
  context: LayerDecisionSchema,
18
19
  experience: LayerDecisionSchema,
19
20
  identity: LayerDecisionSchema,
@@ -1,4 +1,5 @@
1
1
  export * from './common';
2
+ export * from './activity';
2
3
  export * from './context';
3
4
  export * from './experience';
4
5
  export * from './gatekeeper';
@@ -12,6 +12,7 @@ import { attributesCommon } from '@lobechat/observability-otel/node';
12
12
  import { LayersEnum } from '@lobechat/types';
13
13
 
14
14
  import {
15
+ ActivityExtractor,
15
16
  ContextExtractor,
16
17
  ExperienceExtractor,
17
18
  IdentityExtractor,
@@ -32,6 +33,7 @@ import {
32
33
  } from '../types';
33
34
 
34
35
  const LAYER_ORDER: LayersEnum[] = [
36
+ 'activity' as LayersEnum,
35
37
  'identity' as LayersEnum,
36
38
  'context' as LayersEnum,
37
39
  'preference' as LayersEnum,
@@ -39,6 +41,7 @@ const LAYER_ORDER: LayersEnum[] = [
39
41
  ];
40
42
 
41
43
  const LAYER_LABEL_MAP: Record<LayersEnum, string> = {
44
+ activity: 'activities',
42
45
  context: 'contexts',
43
46
  experience: 'experiences',
44
47
  identity: 'identities',
@@ -61,6 +64,7 @@ export interface MemoryExtractionServiceOptions {
61
64
 
62
65
  export interface MemoryExtractionLayerOutputTypes {
63
66
  [LayersEnum.Context]: Awaited<ReturnType<ContextExtractor['structuredCall']>>;
67
+ [LayersEnum.Activity]: Awaited<ReturnType<ActivityExtractor['structuredCall']>>;
64
68
  [LayersEnum.Experience]: Awaited<ReturnType<ExperienceExtractor['structuredCall']>>;
65
69
  [LayersEnum.Preference]: Awaited<ReturnType<PreferenceExtractor['structuredCall']>>;
66
70
  [LayersEnum.Identity]: Awaited<ReturnType<IdentityExtractor['structuredCall']>>;
@@ -79,6 +83,7 @@ export class MemoryExtractionService<RO> {
79
83
  private readonly contextExtractor: ContextExtractor;
80
84
  private readonly experienceExtractor: ExperienceExtractor;
81
85
  private readonly preferenceExtractor: PreferenceExtractor;
86
+ private readonly activityExtractor: ActivityExtractor;
82
87
 
83
88
  private readonly gatekeeperRuntime: ModelRuntime;
84
89
  private readonly layerRuntime: ModelRuntime;
@@ -115,6 +120,9 @@ export class MemoryExtractionService<RO> {
115
120
  this.identityExtractor = new IdentityExtractor(
116
121
  buildExtractorConfig(LayersEnum.Identity, 'layer-identity'),
117
122
  );
123
+ this.activityExtractor = new ActivityExtractor(
124
+ buildExtractorConfig(LayersEnum.Activity, 'layer-activity'),
125
+ );
118
126
  this.contextExtractor = new ContextExtractor(
119
127
  buildExtractorConfig(LayersEnum.Context, 'layer-context'),
120
128
  );
@@ -214,6 +222,7 @@ export class MemoryExtractionService<RO> {
214
222
  const outputs = await this.runLayers(job, layersToExtract, { ...options });
215
223
 
216
224
  const processedLayersCount = {
225
+ activity: outputs.activity?.data ? outputs.activity?.data?.memories?.length : 0,
217
226
  context: outputs.context?.data ? outputs.context?.data?.memories?.length : 0,
218
227
  experience: outputs.experience?.data ? outputs.experience?.data?.memories?.length : 0,
219
228
  identity: outputs.identity?.data
@@ -224,6 +233,7 @@ export class MemoryExtractionService<RO> {
224
233
  preference: outputs.preference?.data ? outputs.preference?.data?.memories?.length : 0,
225
234
  };
226
235
  const processedErrorsCount = {
236
+ activity: outputs.activity?.error ? 1 : 0,
227
237
  context: outputs.context?.error ? 1 : 0,
228
238
  experience: outputs.experience?.error ? 1 : 0,
229
239
  identity: outputs.identity?.error ? 1 : 0,
@@ -281,6 +291,15 @@ export class MemoryExtractionService<RO> {
281
291
  );
282
292
  }
283
293
 
294
+ private async runActivityLayer(job: MemoryExtractionJob, options: ExtractorOptions) {
295
+ return this.runLayerExtractor(job, LayersEnum.Activity, () =>
296
+ this.activityExtractor.structuredCall({
297
+ ...options,
298
+ language: options.language ?? 'English',
299
+ }),
300
+ );
301
+ }
302
+
284
303
  private async runExperienceLayer(job: MemoryExtractionJob, options: ExtractorOptions) {
285
304
  return this.runLayerExtractor(job, LayersEnum.Experience, () =>
286
305
  this.experienceExtractor.structuredCall({
@@ -347,6 +366,12 @@ export class MemoryExtractionService<RO> {
347
366
  | { error: unknown };
348
367
  break;
349
368
  }
369
+ case LayersEnum.Activity: {
370
+ outputs.activity = result as
371
+ | { data: MemoryExtractionLayerOutputTypes[typeof layer] }
372
+ | { error: unknown };
373
+ break;
374
+ }
350
375
  case LayersEnum.Experience: {
351
376
  outputs.experience = result as
352
377
  | { data: MemoryExtractionLayerOutputTypes[typeof layer] }
@@ -374,6 +399,7 @@ export class MemoryExtractionService<RO> {
374
399
  await Promise.all(
375
400
  (
376
401
  [
402
+ LayersEnum.Activity,
377
403
  LayersEnum.Context,
378
404
  LayersEnum.Experience,
379
405
  LayersEnum.Preference,
@@ -407,6 +433,9 @@ export class MemoryExtractionService<RO> {
407
433
  case LayersEnum.Context: {
408
434
  return await this.runContextLayer(job, options);
409
435
  }
436
+ case LayersEnum.Activity: {
437
+ return await this.runActivityLayer(job, options);
438
+ }
410
439
  case LayersEnum.Experience: {
411
440
  return await this.runExperienceLayer(job, options);
412
441
  }
@@ -6,6 +6,7 @@ import type {
6
6
  import type { LayersEnum, MemorySourceType } from '@lobechat/types';
7
7
 
8
8
  import type {
9
+ ActivityExtractor,
9
10
  ContextExtractor,
10
11
  ExperienceExtractor,
11
12
  IdentityExtractor,
@@ -14,6 +15,7 @@ import type {
14
15
 
15
16
  export type MemoryExtractionAgent =
16
17
  | 'gatekeeper'
18
+ | 'layer-activity'
17
19
  | 'layer-context'
18
20
  | 'layer-experience'
19
21
  | 'layer-identity'
@@ -124,6 +126,10 @@ export interface PersistedMemoryResult {
124
126
  }
125
127
 
126
128
  export type MemoryExtractionLayerOutputs = Partial<{
129
+ activity: {
130
+ data?: Awaited<ReturnType<ActivityExtractor['structuredCall']>>;
131
+ error?: unknown;
132
+ };
127
133
  context: {
128
134
  data?: Awaited<ReturnType<ContextExtractor['structuredCall']>>;
129
135
  error?: unknown;
@@ -143,6 +149,7 @@ export type MemoryExtractionLayerOutputs = Partial<{
143
149
  }>;
144
150
 
145
151
  export interface GatekeeperDecision {
152
+ activity: MemoryLayerDecision;
146
153
  context: MemoryLayerDecision;
147
154
  experience: MemoryLayerDecision;
148
155
  identity: MemoryLayerDecision;
@@ -1 +1,2 @@
1
1
  export * from './pageContentContext';
2
+ export * from './pageSelectionContext';
@@ -0,0 +1,28 @@
1
+ import type { PageSelection } from '@lobechat/types';
2
+
3
+ /**
4
+ * Format page selections into a system prompt context
5
+ * Each selection is wrapped in a <selection> tag with metadata
6
+ */
7
+ export const formatPageSelections = (selections: PageSelection[]): string => {
8
+ if (!selections || selections.length === 0) {
9
+ return '';
10
+ }
11
+
12
+ const formattedSelections = selections
13
+ .map((sel) => {
14
+ const lineInfo =
15
+ sel.startLine !== undefined
16
+ ? ` lines="${sel.startLine}-${sel.endLine ?? sel.startLine}"`
17
+ : '';
18
+
19
+ return `<selection ${lineInfo}>
20
+ ${sel.xml}
21
+ </selection>`;
22
+ })
23
+ .join('\n');
24
+
25
+ return `<user_selections count="${selections.length}">
26
+ ${formattedSelections}
27
+ </user_selections>`;
28
+ };
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  import { UIChatMessage } from './message';
4
+ import { PageSelection, PageSelectionSchema } from './message/ui/params';
4
5
  import { OpenAIChatMessage } from './openai/chat';
5
6
  import { LobeUniformTool, LobeUniformToolSchema } from './tool';
6
7
  import { ChatTopic } from './topic';
@@ -10,6 +11,8 @@ export interface SendNewMessage {
10
11
  content: string;
11
12
  // if message has attached with files, then add files to message and the agent
12
13
  files?: string[];
14
+ /** Page selections attached to this message (for Ask AI functionality) */
15
+ pageSelections?: PageSelection[];
13
16
  parentId?: string;
14
17
  }
15
18
 
@@ -83,6 +86,7 @@ export const AiSendMessageServerSchema = z.object({
83
86
  newUserMessage: z.object({
84
87
  content: z.string(),
85
88
  files: z.array(z.string()).optional(),
89
+ pageSelections: z.array(PageSelectionSchema).optional(),
86
90
  parentId: z.string().optional(),
87
91
  }),
88
92
  sessionId: z.string().optional(),
@@ -2,5 +2,6 @@ export * from './base';
2
2
  export * from './image';
3
3
  export * from './messageGroup';
4
4
  export * from './metadata';
5
+ export * from './pageSelection';
5
6
  export * from './tools';
6
7
  export * from './translate';
@@ -1,6 +1,8 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2
2
  import { z } from 'zod';
3
3
 
4
+ import { PageSelection, PageSelectionSchema } from './pageSelection';
5
+
4
6
  export interface ModelTokensUsage {
5
7
  // Input tokens breakdown
6
8
  /**
@@ -80,6 +82,7 @@ export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSche
80
82
  inspectExpanded: z.boolean().optional(),
81
83
  isMultimodal: z.boolean().optional(),
82
84
  isSupervisor: z.boolean().optional(),
85
+ pageSelections: z.array(PageSelectionSchema).optional(),
83
86
  });
84
87
 
85
88
  export interface ModelUsage extends ModelTokensUsage {
@@ -147,4 +150,9 @@ export interface MessageMetadata extends ModelUsage, ModelPerformance {
147
150
  */
148
151
  instruction?: string;
149
152
  taskTitle?: string;
153
+ /**
154
+ * Page selections attached to user message
155
+ * Used for Ask AI functionality to persist selection context
156
+ */
157
+ pageSelections?: PageSelection[];
150
158
  }
@@ -0,0 +1,36 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * Page selection represents a user-selected text region in a page/document.
6
+ * Used for Ask AI functionality to persist selection context with user messages.
7
+ */
8
+ export interface PageSelection {
9
+ /** Selection unique identifier */
10
+ id: string;
11
+ anchor?: {
12
+ startNodeId: string;
13
+ endNodeId: string;
14
+ startOffset: number;
15
+ endOffset: number;
16
+ };
17
+ /** Selected content (plain text or markdown) */
18
+ content: string;
19
+ /** XML structure of the selected content (for positioning edits) */
20
+ xml?: string;
21
+ /** Page ID the selection belongs to */
22
+ pageId: string;
23
+ /** Start line number */
24
+ startLine?: number;
25
+ /** End line number */
26
+ endLine?: number;
27
+ }
28
+
29
+ export const PageSelectionSchema = z.object({
30
+ id: z.string(),
31
+ content: z.string(),
32
+ xml: z.string().optional(),
33
+ pageId: z.string(),
34
+ startLine: z.number().optional(),
35
+ endLine: z.number().optional(),
36
+ });
@@ -5,6 +5,8 @@ import { ConversationContext } from '../../conversation';
5
5
  import { UploadFileItem } from '../../files';
6
6
  import { MessageSemanticSearchChunk } from '../../rag';
7
7
  import { ChatMessageError, ChatMessageErrorSchema } from '../common/base';
8
+ // Import for local use
9
+ import type { PageSelection } from '../common/pageSelection';
8
10
  import { ChatPluginPayload, ToolInterventionSchema } from '../common/tools';
9
11
  import { UIChatMessage } from './chat';
10
12
  import { SemanticSearchChunkSchema } from './rag';
@@ -78,6 +80,10 @@ export interface ChatContextContent {
78
80
  */
79
81
  format?: 'xml' | 'text' | 'markdown';
80
82
  id: string;
83
+ /**
84
+ * Page ID the selection belongs to (for page editor selections)
85
+ */
86
+ pageId?: string;
81
87
  /**
82
88
  * Optional short preview for displaying in UI.
83
89
  */
@@ -86,6 +92,10 @@ export interface ChatContextContent {
86
92
  type: 'text';
87
93
  }
88
94
 
95
+ // Re-export PageSelection from common for backwards compatibility
96
+ export type { PageSelection } from '../common/pageSelection';
97
+ export { PageSelectionSchema } from '../common/pageSelection';
98
+
89
99
  export interface SendMessageParams {
90
100
  /**
91
101
  * create a thread
@@ -119,8 +129,14 @@ export interface SendMessageParams {
119
129
  parentId?: string;
120
130
  /**
121
131
  * Additional contextual snippets (e.g., text selections) attached to the request.
132
+ * @deprecated Use pageSelections instead for page editor selections
122
133
  */
123
134
  contexts?: ChatContextContent[];
135
+ /**
136
+ * Page selections attached to the message (for Ask AI functionality)
137
+ * These will be persisted to the database and injected via context-engine
138
+ */
139
+ pageSelections?: PageSelection[];
124
140
  }
125
141
 
126
142
  export interface SendGroupMessageParams {
@@ -10,7 +10,7 @@ import {
10
10
  UserSystemAgentConfig,
11
11
  } from './user/settings';
12
12
 
13
- export type GlobalMemoryLayer = 'context' | 'experience' | 'identity' | 'preference';
13
+ export type GlobalMemoryLayer = 'activity' | 'context' | 'experience' | 'identity' | 'preference';
14
14
 
15
15
  export interface MemoryAgentPublicConfig {
16
16
  baseURL?: string;