@lobehub/lobehub 2.0.0-next.250 → 2.0.0-next.252

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 (60) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +14 -0
  3. package/package.json +1 -1
  4. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
  5. package/packages/database/migrations/meta/_journal.json +1 -1
  6. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
  7. package/packages/database/src/models/topic.ts +71 -4
  8. package/packages/database/src/schemas/agentCronJob.ts +1 -2
  9. package/packages/memory-user-memory/src/extractors/context.ts +1 -4
  10. package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
  11. package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
  12. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
  13. package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
  14. package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
  15. package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
  16. package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
  17. package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
  18. package/packages/memory-user-memory/src/types.ts +10 -8
  19. package/packages/types/src/discover/mcp.ts +1 -1
  20. package/packages/types/src/topic/topic.ts +9 -0
  21. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
  22. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
  23. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
  24. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
  25. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
  26. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
  27. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
  28. package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
  29. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
  30. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
  31. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
  32. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
  33. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
  34. package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
  35. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +6 -0
  36. package/src/app/[variants]/(main)/community/(list)/assistant/features/List/Item.tsx +12 -3
  37. package/src/app/[variants]/(main)/community/(list)/mcp/features/List/Item.tsx +14 -4
  38. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  39. package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +1 -1
  40. package/src/hooks/useFetchCronTopics.ts +29 -0
  41. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
  42. package/src/hooks/useFetchTopics.ts +4 -1
  43. package/src/locales/default/setting.ts +44 -1
  44. package/src/server/routers/lambda/agentCronJob.ts +367 -0
  45. package/src/server/routers/lambda/image/index.test.ts +2 -2
  46. package/src/server/routers/lambda/index.ts +2 -0
  47. package/src/server/routers/lambda/market/index.ts +45 -4
  48. package/src/server/routers/lambda/topic.ts +15 -3
  49. package/src/server/services/aiAgent/index.ts +18 -1
  50. package/src/server/services/discover/index.ts +29 -3
  51. package/src/server/services/memory/userMemory/extract.ts +14 -6
  52. package/src/services/agentCronJob.ts +95 -0
  53. package/src/services/discover.ts +38 -1
  54. package/src/services/topic/index.ts +1 -0
  55. package/src/store/chat/slices/topic/action.ts +53 -2
  56. package/src/store/chat/slices/topic/initialState.ts +1 -0
  57. package/src/store/chat/slices/topic/selectors.ts +14 -6
  58. package/src/store/tool/slices/mcpStore/action.test.ts +38 -0
  59. package/src/store/tool/slices/mcpStore/action.ts +18 -0
  60. package/src/tools/placeholders.ts +1 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.252](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.251...v2.0.0-next.252)
6
+
7
+ <sup>Released on **2026-01-09**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Add the agent cron job.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Add the agent cron job, closes [#11370](https://github.com/lobehub/lobe-chat/issues/11370) ([10e47d9](https://github.com/lobehub/lobe-chat/commit/10e47d9))
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.251](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.250...v2.0.0-next.251)
31
+
32
+ <sup>Released on **2026-01-09**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **community**: Support to report for agent & mcp plugin interaction for recommendation.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **community**: Support to report for agent & mcp plugin interaction for recommendation, closes [#11289](https://github.com/lobehub/lobe-chat/issues/11289) ([6f98792](https://github.com/lobehub/lobe-chat/commit/6f98792))
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.250](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.249...v2.0.0-next.250)
6
56
 
7
57
  <sup>Released on **2026-01-09**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Add the agent cron job."
6
+ ]
7
+ },
8
+ "date": "2026-01-09",
9
+ "version": "2.0.0-next.252"
10
+ },
11
+ {
12
+ "children": {},
13
+ "date": "2026-01-09",
14
+ "version": "2.0.0-next.251"
15
+ },
2
16
  {
3
17
  "children": {},
4
18
  "date": "2026-01-09",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.250",
3
+ "version": "2.0.0-next.252",
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",
@@ -11,7 +11,6 @@ import { useChatStore } from '@/store/chat';
11
11
 
12
12
  import { NotebookDocument } from '../../../types';
13
13
 
14
-
15
14
  const styles = createStaticStyles(({ css, cssVar }) => ({
16
15
  container: css`
17
16
  position: relative;
@@ -26,7 +25,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
26
25
  `,
27
26
  content: css`
28
27
  padding-inline: 16px;
29
-
30
28
  font-size: 14px;
31
29
  `,
32
30
  expandButton: css`
@@ -479,4 +479,4 @@
479
479
  }
480
480
  ],
481
481
  "version": "6"
482
- }
482
+ }
@@ -1,10 +1,10 @@
1
1
  import { eq, inArray } from 'drizzle-orm';
2
2
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
3
 
4
+ import { getTestDB } from '../../../core/getTestDB';
4
5
  import { agents, messages, sessions, topics, users } from '../../../schemas';
5
6
  import { LobeChatDatabase } from '../../../type';
6
7
  import { CreateTopicParams, TopicModel } from '../../topic';
7
- import { getTestDB } from '../../../core/getTestDB';
8
8
 
9
9
  const userId = 'topic-create-user';
10
10
  const userId2 = 'topic-create-user-2';
@@ -68,11 +68,17 @@ describe('TopicModel - Create', () => {
68
68
  expect(dbTopic).toHaveLength(1);
69
69
  expect(dbTopic[0]).toEqual(createdTopic);
70
70
 
71
- const associatedMessages = await serverDB.select().from(messages).where(inArray(messages.id, topicData.messages!));
71
+ const associatedMessages = await serverDB
72
+ .select()
73
+ .from(messages)
74
+ .where(inArray(messages.id, topicData.messages!));
72
75
  expect(associatedMessages).toHaveLength(2);
73
76
  expect(associatedMessages.every((msg) => msg.topicId === topicId)).toBe(true);
74
77
 
75
- const unassociatedMessage = await serverDB.select().from(messages).where(eq(messages.id, 'message3'));
78
+ const unassociatedMessage = await serverDB
79
+ .select()
80
+ .from(messages)
81
+ .where(eq(messages.id, 'message3'));
76
82
  expect(unassociatedMessage[0].topicId).toBeNull();
77
83
  });
78
84
 
@@ -169,8 +175,18 @@ describe('TopicModel - Create', () => {
169
175
  const createdTopics = await topicModel.batchCreate(topicParams);
170
176
 
171
177
  expect(createdTopics).toHaveLength(2);
172
- expect(createdTopics[0]).toMatchObject({ title: 'Topic 1', favorite: true, sessionId, userId });
173
- expect(createdTopics[1]).toMatchObject({ title: 'Topic 2', favorite: false, sessionId, userId });
178
+ expect(createdTopics[0]).toMatchObject({
179
+ title: 'Topic 1',
180
+ favorite: true,
181
+ sessionId,
182
+ userId,
183
+ });
184
+ expect(createdTopics[1]).toMatchObject({
185
+ title: 'Topic 2',
186
+ favorite: false,
187
+ sessionId,
188
+ userId,
189
+ });
174
190
 
175
191
  const items = await serverDB.select().from(topics);
176
192
  expect(items).toHaveLength(2);
@@ -216,7 +232,15 @@ describe('TopicModel - Create', () => {
216
232
  expect(createdTopics[1].agentId).toBe('batch-agent-2');
217
233
  expect(createdTopics[1].sessionId).toBeNull();
218
234
 
219
- const dbTopics = await serverDB.select().from(topics).where(inArray(topics.id, createdTopics.map((t) => t.id)));
235
+ const dbTopics = await serverDB
236
+ .select()
237
+ .from(topics)
238
+ .where(
239
+ inArray(
240
+ topics.id,
241
+ createdTopics.map((t) => t.id),
242
+ ),
243
+ );
220
244
  expect(dbTopics).toHaveLength(2);
221
245
  expect(dbTopics.find((t) => t.id === createdTopics[0].id)?.agentId).toBe('batch-agent-1');
222
246
  expect(dbTopics.find((t) => t.id === createdTopics[1].id)?.agentId).toBe('batch-agent-2');
@@ -236,7 +260,10 @@ describe('TopicModel - Create', () => {
236
260
  ]);
237
261
  });
238
262
 
239
- const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(topicId, newTitle);
263
+ const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(
264
+ topicId,
265
+ newTitle,
266
+ );
240
267
 
241
268
  expect(duplicatedTopic.id).not.toBe(topicId);
242
269
  expect(duplicatedTopic.title).toBe(newTitle);
@@ -255,7 +282,9 @@ describe('TopicModel - Create', () => {
255
282
  it('should throw an error if the topic to duplicate does not exist', async () => {
256
283
  const topicId = 'nonexistent-topic';
257
284
 
258
- await expect(topicModel.duplicate(topicId)).rejects.toThrow(`Topic with id ${topicId} not found`);
285
+ await expect(topicModel.duplicate(topicId)).rejects.toThrow(
286
+ `Topic with id ${topicId} not found`,
287
+ );
259
288
  });
260
289
  });
261
290
  });
@@ -27,8 +27,10 @@ export interface CreateTopicParams {
27
27
  favorite?: boolean;
28
28
  groupId?: string | null;
29
29
  messages?: string[];
30
+ metadata?: ChatTopicMetadata;
30
31
  sessionId?: string | null;
31
32
  title?: string;
33
+ trigger?: string | null;
32
34
  }
33
35
 
34
36
  interface QueryTopicParams {
@@ -39,6 +41,10 @@ interface QueryTopicParams {
39
41
  */
40
42
  containerId?: string | null;
41
43
  current?: number;
44
+ /**
45
+ * Exclude topics by trigger types (e.g. ['cron'])
46
+ */
47
+ excludeTriggers?: string[];
42
48
  /**
43
49
  * Group ID to filter topics by
44
50
  */
@@ -70,15 +76,24 @@ export class TopicModel {
70
76
  agentId,
71
77
  containerId,
72
78
  current = 0,
79
+ excludeTriggers,
73
80
  pageSize = 9999,
74
81
  groupId,
75
82
  isInbox,
76
83
  }: QueryTopicParams = {}) => {
77
84
  const offset = current * pageSize;
85
+ const excludeTriggerCondition =
86
+ excludeTriggers && excludeTriggers.length > 0
87
+ ? or(isNull(topics.trigger), not(inArray(topics.trigger, excludeTriggers)))
88
+ : undefined;
78
89
 
79
90
  // If groupId is provided, query topics by groupId directly
80
91
  if (groupId) {
81
- const whereCondition = and(eq(topics.userId, this.userId), eq(topics.groupId, groupId));
92
+ const whereCondition = and(
93
+ eq(topics.userId, this.userId),
94
+ eq(topics.groupId, groupId),
95
+ excludeTriggerCondition,
96
+ );
82
97
 
83
98
  const [items, totalResult] = await Promise.all([
84
99
  this.db
@@ -155,21 +170,25 @@ export class TopicModel {
155
170
  updatedAt: topics.updatedAt,
156
171
  })
157
172
  .from(topics)
158
- .where(and(eq(topics.userId, this.userId), agentCondition))
173
+ .where(and(eq(topics.userId, this.userId), agentCondition, excludeTriggerCondition))
159
174
  .orderBy(desc(topics.favorite), desc(topics.updatedAt))
160
175
  .limit(pageSize)
161
176
  .offset(offset),
162
177
  this.db
163
178
  .select({ count: count(topics.id) })
164
179
  .from(topics)
165
- .where(and(eq(topics.userId, this.userId), agentCondition)),
180
+ .where(and(eq(topics.userId, this.userId), agentCondition, excludeTriggerCondition)),
166
181
  ]);
167
182
 
168
183
  return { items, total: totalResult[0].count };
169
184
  }
170
185
 
171
186
  // Fallback to containerId-based query (backward compatibility)
172
- const whereCondition = and(eq(topics.userId, this.userId), this.matchContainer(containerId));
187
+ const whereCondition = and(
188
+ eq(topics.userId, this.userId),
189
+ this.matchContainer(containerId),
190
+ excludeTriggerCondition,
191
+ );
173
192
 
174
193
  const [items, totalResult] = await Promise.all([
175
194
  this.db
@@ -664,4 +683,52 @@ export class TopicModel {
664
683
  ),
665
684
  });
666
685
  };
686
+
687
+ /**
688
+ * Get cron topics grouped by cronJob for a specific agent
689
+ * Returns topics where trigger='cron' and metadata contains cronJobId
690
+ */
691
+ getCronTopicsGroupedByCronJob = async (agentId: string) => {
692
+ const cronTopics = await this.db
693
+ .select({
694
+ createdAt: topics.createdAt,
695
+ favorite: topics.favorite,
696
+ historySummary: topics.historySummary,
697
+ id: topics.id,
698
+ metadata: topics.metadata,
699
+ title: topics.title,
700
+ trigger: topics.trigger,
701
+ updatedAt: topics.updatedAt,
702
+ })
703
+ .from(topics)
704
+ .where(
705
+ and(
706
+ eq(topics.userId, this.userId),
707
+ eq(topics.agentId, agentId),
708
+ eq(topics.trigger, 'cron'),
709
+ // Check if metadata contains cronJobId
710
+ sql`${topics.metadata}->>'cronJobId' IS NOT NULL`,
711
+ ),
712
+ )
713
+ .orderBy(desc(topics.updatedAt));
714
+
715
+ // Group topics by cronJobId
716
+ const groupedTopics = new Map<string, typeof cronTopics>();
717
+
718
+ cronTopics.forEach((topic) => {
719
+ const cronJobId = topic.metadata?.cronJobId;
720
+ if (cronJobId) {
721
+ if (!groupedTopics.has(cronJobId)) {
722
+ groupedTopics.set(cronJobId, []);
723
+ }
724
+ groupedTopics.get(cronJobId)!.push(topic);
725
+ }
726
+ });
727
+
728
+ // Convert Map to array of grouped objects
729
+ return Array.from(groupedTopics.entries()).map(([cronJobId, topicList]) => ({
730
+ cronJobId,
731
+ topics: topicList,
732
+ }));
733
+ };
667
734
  }
@@ -32,8 +32,7 @@ export const agentCronJobs = pgTable(
32
32
  agentId: text('agent_id')
33
33
  .references(() => agents.id, { onDelete: 'cascade' })
34
34
  .notNull(),
35
- groupId: text('group_id')
36
- .references(() => chatGroups.id, { onDelete: 'cascade' }),
35
+ groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'cascade' }),
37
36
  userId: text('user_id')
38
37
  .references(() => users.id, { onDelete: 'cascade' })
39
38
  .notNull(),
@@ -16,10 +16,7 @@ export class ContextExtractor extends BaseMemoryExtractor<ContextMemory> {
16
16
  }
17
17
 
18
18
  getSchema() {
19
- return buildGenerateObjectSchema(
20
- ContextMemorySchema,
21
- { name: 'context_extraction' },
22
- );
19
+ return buildGenerateObjectSchema(ContextMemorySchema, { name: 'context_extraction' });
23
20
  }
24
21
 
25
22
  getResultSchema() {
@@ -1,10 +1,7 @@
1
1
  import { renderPlaceholderTemplate } from '@lobechat/context-engine';
2
2
 
3
3
  import { experiencePrompt } from '../prompts';
4
- import {
5
- ExperienceMemory,
6
- ExperienceMemorySchema
7
- } from '../schemas';
4
+ import { ExperienceMemory, ExperienceMemorySchema } from '../schemas';
8
5
  import { ExtractorTemplateProps } from '../types';
9
6
  import { buildGenerateObjectSchema } from '../utils/zod';
10
7
  import { BaseMemoryExtractor } from './base';
@@ -19,10 +16,7 @@ export class ExperienceExtractor extends BaseMemoryExtractor<ExperienceMemory> {
19
16
  }
20
17
 
21
18
  getSchema() {
22
- return buildGenerateObjectSchema(
23
- ExperienceMemorySchema,
24
- { name: 'experience_extraction' },
25
- );
19
+ return buildGenerateObjectSchema(ExperienceMemorySchema, { name: 'experience_extraction' });
26
20
  }
27
21
 
28
22
  getResultSchema() {
@@ -1,10 +1,7 @@
1
1
  import { renderPlaceholderTemplate } from '@lobechat/context-engine';
2
2
 
3
- import {
4
- PreferenceMemory,
5
- PreferenceMemorySchema,
6
- } from '../schemas';
7
3
  import { preferencePrompt } from '../prompts';
4
+ import { PreferenceMemory, PreferenceMemorySchema } from '../schemas';
8
5
  import { ExtractorTemplateProps } from '../types';
9
6
  import { buildGenerateObjectSchema } from '../utils/zod';
10
7
  import { BaseMemoryExtractor } from './base';
@@ -19,10 +16,7 @@ export class PreferenceExtractor extends BaseMemoryExtractor<PreferenceMemory> {
19
16
  }
20
17
 
21
18
  getSchema() {
22
- return buildGenerateObjectSchema(
23
- PreferenceMemorySchema,
24
- { name: 'preference_extraction' },
25
- );
19
+ return buildGenerateObjectSchema(PreferenceMemorySchema, { name: 'preference_extraction' });
26
20
  }
27
21
 
28
22
  getResultSchema() {