@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
@@ -0,0 +1,367 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { z } from 'zod';
3
+
4
+ import { AgentCronJobModel } from '@/database/models/agentCronJob';
5
+ import {
6
+ type CreateAgentCronJobData,
7
+ type UpdateAgentCronJobData,
8
+ insertAgentCronJobSchema,
9
+ updateAgentCronJobSchema,
10
+ } from '@/database/schemas/agentCronJob';
11
+ import { authedProcedure, router } from '@/libs/trpc/lambda';
12
+ import { serverDatabase } from '@/libs/trpc/lambda/middleware';
13
+
14
+ const agentCronJobProcedure = authedProcedure.use(serverDatabase);
15
+
16
+ const listQuerySchema = z.object({
17
+ agentId: z.string().optional(),
18
+ enabled: z.boolean().optional(),
19
+ limit: z.number().min(1).max(100).default(20),
20
+ offset: z.number().min(0).default(0),
21
+ });
22
+
23
+ const resetExecutionsSchema = z.object({
24
+ id: z.string(),
25
+ newMaxExecutions: z.number().min(1).max(10_000).optional(),
26
+ });
27
+
28
+ const batchUpdateStatusSchema = z.object({
29
+ enabled: z.boolean(),
30
+ ids: z.array(z.string()),
31
+ });
32
+
33
+ // Create input schema for tRPC that omits server-managed fields
34
+ const createAgentCronJobInputSchema = insertAgentCronJobSchema.omit({
35
+ userId: true, // Provided by authentication context
36
+ });
37
+
38
+ /**
39
+ * Agent Cron Job tRPC Router
40
+ *
41
+ * Provides type-safe API for managing agent scheduled tasks
42
+ */
43
+ export const agentCronJobRouter = router({
44
+ /**
45
+ * Batch update status (enable/disable) for multiple jobs
46
+ */
47
+ batchUpdateStatus: agentCronJobProcedure
48
+ .input(batchUpdateStatusSchema)
49
+ .mutation(async ({ input, ctx }) => {
50
+ const { userId, serverDB: db } = ctx;
51
+ const { ids, enabled } = input;
52
+
53
+ try {
54
+ const cronJobModel = new AgentCronJobModel(db, userId);
55
+ const updatedCount = await cronJobModel.batchUpdateStatus(ids, enabled);
56
+
57
+ return {
58
+ data: { updatedCount },
59
+ message: `${updatedCount} cron jobs ${enabled ? 'enabled' : 'disabled'} successfully`,
60
+ success: true,
61
+ };
62
+ } catch (error) {
63
+ console.error('[agentCronJob:batchUpdateStatus]', error);
64
+ throw new TRPCError({
65
+ cause: error,
66
+ code: 'INTERNAL_SERVER_ERROR',
67
+ message: 'Failed to update cron job statuses',
68
+ });
69
+ }
70
+ }),
71
+
72
+ /**
73
+ * Create a new cron job
74
+ */
75
+ create: agentCronJobProcedure
76
+ .input(createAgentCronJobInputSchema)
77
+ .mutation(async ({ input, ctx }) => {
78
+ const { userId, serverDB: db } = ctx;
79
+
80
+ try {
81
+ const cronJobModel = new AgentCronJobModel(db, userId);
82
+ // Add userId to the input data since it's provided by authentication context
83
+ const cronJobData = { ...input, userId };
84
+ const cronJob = await cronJobModel.create(cronJobData as CreateAgentCronJobData);
85
+
86
+ return {
87
+ data: cronJob,
88
+ message: 'Cron job created successfully',
89
+ success: true,
90
+ };
91
+ } catch (error) {
92
+ console.error('[agentCronJob:create]', error);
93
+ throw new TRPCError({
94
+ cause: error,
95
+ code: 'INTERNAL_SERVER_ERROR',
96
+ message: 'Failed to create cron job',
97
+ });
98
+ }
99
+ }),
100
+
101
+ /**
102
+ * Delete a cron job
103
+ */
104
+ delete: agentCronJobProcedure
105
+ .input(z.object({ id: z.string() }))
106
+ .mutation(async ({ input, ctx }) => {
107
+ const { userId, serverDB: db } = ctx;
108
+ const { id } = input;
109
+
110
+ try {
111
+ const cronJobModel = new AgentCronJobModel(db, userId);
112
+ const deleted = await cronJobModel.delete(id);
113
+
114
+ if (!deleted) {
115
+ throw new TRPCError({
116
+ code: 'NOT_FOUND',
117
+ message: 'Cron job not found or access denied',
118
+ });
119
+ }
120
+
121
+ return {
122
+ message: 'Cron job deleted successfully',
123
+ success: true,
124
+ };
125
+ } catch (error) {
126
+ if (error instanceof TRPCError) throw error;
127
+
128
+ console.error('[agentCronJob:delete]', error);
129
+ throw new TRPCError({
130
+ cause: error,
131
+ code: 'INTERNAL_SERVER_ERROR',
132
+ message: 'Failed to delete cron job',
133
+ });
134
+ }
135
+ }),
136
+
137
+ /**
138
+ * List cron jobs by agent ID
139
+ */
140
+ findByAgent: agentCronJobProcedure
141
+ .input(z.object({ agentId: z.string() }))
142
+ .query(async ({ input, ctx }) => {
143
+ const { userId, serverDB: db } = ctx;
144
+ const { agentId } = input;
145
+
146
+ try {
147
+ const cronJobModel = new AgentCronJobModel(db, userId);
148
+ const cronJobs = await cronJobModel.findByAgentId(agentId);
149
+
150
+ return {
151
+ data: cronJobs,
152
+ success: true,
153
+ };
154
+ } catch (error) {
155
+ console.error('[agentCronJob:findByAgent]', error);
156
+ throw new TRPCError({
157
+ cause: error,
158
+ code: 'INTERNAL_SERVER_ERROR',
159
+ message: 'Failed to fetch agent cron jobs',
160
+ });
161
+ }
162
+ }),
163
+
164
+ /**
165
+ * Get a single cron job by ID
166
+ */
167
+ findById: agentCronJobProcedure
168
+ .input(z.object({ id: z.string() }))
169
+ .query(async ({ input, ctx }) => {
170
+ const { userId, serverDB: db } = ctx;
171
+ const { id } = input;
172
+
173
+ try {
174
+ const cronJobModel = new AgentCronJobModel(db, userId);
175
+ const cronJob = await cronJobModel.findById(id);
176
+
177
+ if (!cronJob) {
178
+ throw new TRPCError({
179
+ code: 'NOT_FOUND',
180
+ message: 'Cron job not found',
181
+ });
182
+ }
183
+
184
+ return {
185
+ data: cronJob,
186
+ success: true,
187
+ };
188
+ } catch (error) {
189
+ if (error instanceof TRPCError) throw error;
190
+
191
+ console.error('[agentCronJob:findById]', error);
192
+ throw new TRPCError({
193
+ cause: error,
194
+ code: 'INTERNAL_SERVER_ERROR',
195
+ message: 'Failed to fetch cron job',
196
+ });
197
+ }
198
+ }),
199
+
200
+ /**
201
+ * Get jobs that are near depletion (for warnings)
202
+ */
203
+ getNearDepletion: agentCronJobProcedure
204
+ .input(z.object({ threshold: z.number().min(1).max(20).default(5) }))
205
+ .query(async ({ input, ctx }) => {
206
+ const { userId, serverDB: db } = ctx;
207
+ const { threshold } = input;
208
+
209
+ try {
210
+ const cronJobModel = new AgentCronJobModel(db, userId);
211
+ const jobs = await cronJobModel.getTasksNearDepletion(threshold);
212
+
213
+ return {
214
+ data: jobs,
215
+ success: true,
216
+ };
217
+ } catch (error) {
218
+ console.error('[agentCronJob:getNearDepletion]', error);
219
+ throw new TRPCError({
220
+ cause: error,
221
+ code: 'INTERNAL_SERVER_ERROR',
222
+ message: 'Failed to fetch near depletion jobs',
223
+ });
224
+ }
225
+ }),
226
+
227
+ /**
228
+ * Get execution statistics for user's cron jobs
229
+ */
230
+ getStats: agentCronJobProcedure.query(async ({ ctx }) => {
231
+ const { userId, serverDB: db } = ctx;
232
+
233
+ try {
234
+ const cronJobModel = new AgentCronJobModel(db, userId);
235
+ const stats = await cronJobModel.getExecutionStats();
236
+
237
+ return {
238
+ data: stats,
239
+ success: true,
240
+ };
241
+ } catch (error) {
242
+ console.error('[agentCronJob:getStats]', error);
243
+ throw new TRPCError({
244
+ cause: error,
245
+ code: 'INTERNAL_SERVER_ERROR',
246
+ message: 'Failed to fetch execution statistics',
247
+ });
248
+ }
249
+ }),
250
+
251
+ /**
252
+ * List cron jobs with filtering and pagination
253
+ */
254
+ list: agentCronJobProcedure.input(listQuerySchema).query(async ({ input, ctx }) => {
255
+ const { userId, serverDB: db } = ctx;
256
+ const { agentId, enabled, limit, offset } = input;
257
+
258
+ try {
259
+ const cronJobModel = new AgentCronJobModel(db, userId);
260
+ const result = await cronJobModel.findWithPagination({
261
+ agentId,
262
+ enabled,
263
+ limit,
264
+ offset,
265
+ });
266
+
267
+ return {
268
+ data: result.jobs,
269
+ pagination: {
270
+ hasMore: offset + limit < result.total,
271
+ limit,
272
+ offset,
273
+ total: result.total,
274
+ },
275
+ success: true,
276
+ };
277
+ } catch (error) {
278
+ console.error('[agentCronJob:list]', error);
279
+ throw new TRPCError({
280
+ cause: error,
281
+ code: 'INTERNAL_SERVER_ERROR',
282
+ message: 'Failed to fetch cron jobs',
283
+ });
284
+ }
285
+ }),
286
+
287
+ /**
288
+ * Reset execution counts for a cron job
289
+ */
290
+ resetExecutions: agentCronJobProcedure
291
+ .input(resetExecutionsSchema)
292
+ .mutation(async ({ input, ctx }) => {
293
+ const { userId, serverDB: db } = ctx;
294
+ const { id, newMaxExecutions } = input;
295
+
296
+ try {
297
+ const cronJobModel = new AgentCronJobModel(db, userId);
298
+ const cronJob = await cronJobModel.resetExecutions(id, newMaxExecutions);
299
+
300
+ if (!cronJob) {
301
+ throw new TRPCError({
302
+ code: 'NOT_FOUND',
303
+ message: 'Cron job not found or access denied',
304
+ });
305
+ }
306
+
307
+ return {
308
+ data: cronJob,
309
+ message: 'Execution counts reset successfully',
310
+ success: true,
311
+ };
312
+ } catch (error) {
313
+ if (error instanceof TRPCError) throw error;
314
+
315
+ console.error('[agentCronJob:resetExecutions]', error);
316
+ throw new TRPCError({
317
+ cause: error,
318
+ code: 'INTERNAL_SERVER_ERROR',
319
+ message: 'Failed to reset execution counts',
320
+ });
321
+ }
322
+ }),
323
+
324
+ /**
325
+ * Update a cron job
326
+ */
327
+ update: agentCronJobProcedure
328
+ .input(
329
+ z.object({
330
+ data: updateAgentCronJobSchema,
331
+ id: z.string(),
332
+ }),
333
+ )
334
+ .mutation(async ({ input, ctx }) => {
335
+ const { userId, serverDB: db } = ctx;
336
+ const { id, data } = input;
337
+
338
+ try {
339
+ const cronJobModel = new AgentCronJobModel(db, userId);
340
+ const cronJob = await cronJobModel.update(id, data as UpdateAgentCronJobData);
341
+
342
+ if (!cronJob) {
343
+ throw new TRPCError({
344
+ code: 'NOT_FOUND',
345
+ message: 'Cron job not found or access denied',
346
+ });
347
+ }
348
+
349
+ return {
350
+ data: cronJob,
351
+ message: 'Cron job updated successfully',
352
+ success: true,
353
+ };
354
+ } catch (error) {
355
+ if (error instanceof TRPCError) throw error;
356
+
357
+ console.error('[agentCronJob:update]', error);
358
+ throw new TRPCError({
359
+ cause: error,
360
+ code: 'INTERNAL_SERVER_ERROR',
361
+ message: 'Failed to update cron job',
362
+ });
363
+ }
364
+ }),
365
+
366
+ // Note: testExecution moved to cloud layer since it uses AgentCronWorkflow
367
+ });
@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import { AsyncTaskStatus, AsyncTaskType } from '@/types/asyncTask';
4
4
 
5
+ import { imageRouter } from './index';
6
+
5
7
  // Use vi.hoisted for variables used in vi.mock factory
6
8
  const {
7
9
  mockServerDB,
@@ -79,8 +81,6 @@ vi.mock('@/utils/number', () => ({
79
81
  generateUniqueSeeds: vi.fn((count: number) => Array.from({ length: count }, (_, i) => 1000 + i)),
80
82
  }));
81
83
 
82
- import { imageRouter } from './index';
83
-
84
84
  describe('imageRouter', () => {
85
85
  const mockUserId = 'test-user-id';
86
86
  const mockAsyncCallerCreateImage = vi.fn();
@@ -8,6 +8,7 @@ import { topUpRouter } from '@/business/server/lambda-routers/topUp';
8
8
  import { publicProcedure, router } from '@/libs/trpc/lambda';
9
9
 
10
10
  import { agentRouter } from './agent';
11
+ import { agentCronJobRouter } from './agentCronJob';
11
12
  import { agentGroupRouter } from './agentGroup';
12
13
  import { aiAgentRouter } from './aiAgent';
13
14
  import { aiChatRouter } from './aiChat';
@@ -46,6 +47,7 @@ import { userMemoryRouter } from './userMemory';
46
47
 
47
48
  export const lambdaRouter = router({
48
49
  agent: agentRouter,
50
+ agentCronJob: agentCronJobRouter,
49
51
  aiAgent: aiAgentRouter,
50
52
  aiChat: aiChatRouter,
51
53
  aiModel: aiModelRouter,
@@ -167,7 +167,7 @@ export const marketRouter = router({
167
167
  }),
168
168
 
169
169
  // ============================== MCP Market ==============================
170
- getMcpCategories: marketProcedure
170
+ getMcpCategories: marketProcedure
171
171
  .input(
172
172
  z
173
173
  .object({
@@ -351,7 +351,7 @@ export const marketRouter = router({
351
351
  }),
352
352
 
353
353
  // ============================== Plugin Market ==============================
354
- getPluginCategories: marketProcedure
354
+ getPluginCategories: marketProcedure
355
355
  .input(
356
356
  z
357
357
  .object({
@@ -439,7 +439,7 @@ export const marketRouter = router({
439
439
  }),
440
440
 
441
441
  // ============================== Providers ==============================
442
- getProviderDetail: marketProcedure
442
+ getProviderDetail: marketProcedure
443
443
  .input(
444
444
  z.object({
445
445
  identifier: z.string(),
@@ -503,7 +503,7 @@ export const marketRouter = router({
503
503
  }),
504
504
 
505
505
  // ============================== User Profile ==============================
506
- getUserInfo: marketProcedure
506
+ getUserInfo: marketProcedure
507
507
  .input(
508
508
  z.object({
509
509
  locale: z.string().optional(),
@@ -598,6 +598,26 @@ export const marketRouter = router({
598
598
  }
599
599
  }),
600
600
 
601
+ reportAgentEvent: marketProcedure
602
+ .input(
603
+ z.object({
604
+ event: z.enum(['add', 'chat', 'click']),
605
+ identifier: z.string(),
606
+ source: z.string().optional(),
607
+ }),
608
+ )
609
+ .mutation(async ({ input, ctx }) => {
610
+ log('createAgentEvent input: %O', input);
611
+
612
+ try {
613
+ await ctx.discoverService.createAgentEvent(input);
614
+ return { success: true };
615
+ } catch (error) {
616
+ console.error('Error reporting Agent event: %O', error);
617
+ return { success: false };
618
+ }
619
+ }),
620
+
601
621
  reportAgentInstall: marketProcedure
602
622
  .input(
603
623
  z.object({
@@ -655,6 +675,27 @@ export const marketRouter = router({
655
675
  }
656
676
  }),
657
677
 
678
+
679
+ reportMcpEvent: marketProcedure
680
+ .input(
681
+ z.object({
682
+ event: z.enum(['click', 'install', 'activate', 'uninstall']),
683
+ identifier: z.string(),
684
+ source: z.string().optional(),
685
+ }),
686
+ )
687
+ .mutation(async ({ input, ctx }) => {
688
+ log('createMcpEvent input: %O', input);
689
+
690
+ try {
691
+ await ctx.discoverService.createPluginEvent(input);
692
+ return { success: true };
693
+ } catch (error) {
694
+ console.error('Error reporting MCP event: %O', error);
695
+ return { success: false };
696
+ }
697
+ }),
698
+
658
699
  reportMcpInstallResult: marketProcedure
659
700
  .input(
660
701
  z.object({
@@ -142,11 +142,18 @@ export const topicRouter = router({
142
142
  return ctx.topicModel.queryAll();
143
143
  }),
144
144
 
145
+ getCronTopicsGroupedByCronJob: topicProcedure
146
+ .input(z.object({ agentId: z.string() }))
147
+ .query(async ({ input, ctx }) => {
148
+ return ctx.topicModel.getCronTopicsGroupedByCronJob(input.agentId);
149
+ }),
150
+
145
151
  getTopics: topicProcedure
146
152
  .input(
147
153
  z.object({
148
154
  agentId: z.string().nullable().optional(),
149
155
  current: z.number().optional(),
156
+ excludeTriggers: z.array(z.string()).optional(),
150
157
  groupId: z.string().nullable().optional(),
151
158
  isInbox: z.boolean().optional(),
152
159
  pageSize: z.number().optional(),
@@ -154,11 +161,11 @@ export const topicRouter = router({
154
161
  }),
155
162
  )
156
163
  .query(async ({ input, ctx }) => {
157
- const { sessionId, isInbox, groupId, ...rest } = input;
164
+ const { sessionId, isInbox, groupId, excludeTriggers, ...rest } = input;
158
165
 
159
166
  // If groupId is provided, query by groupId directly
160
167
  if (groupId) {
161
- const result = await ctx.topicModel.query({ groupId, ...rest });
168
+ const result = await ctx.topicModel.query({ excludeTriggers, groupId, ...rest });
162
169
  return { items: result.items, total: result.total };
163
170
  }
164
171
 
@@ -168,7 +175,12 @@ export const topicRouter = router({
168
175
  effectiveAgentId = await resolveAgentIdFromSession(sessionId, ctx.serverDB, ctx.userId);
169
176
  }
170
177
 
171
- const result = await ctx.topicModel.query({ ...rest, agentId: effectiveAgentId, isInbox });
178
+ const result = await ctx.topicModel.query({
179
+ ...rest,
180
+ agentId: effectiveAgentId,
181
+ excludeTriggers,
182
+ isInbox,
183
+ });
172
184
 
173
185
  // Runtime migration: backfill agentId for ALL legacy topics and messages under this agent
174
186
  const runMigration = async () => {
@@ -33,8 +33,12 @@ const log = debug('lobe-server:ai-agent-service');
33
33
  * This extends the public ExecAgentParams with server-side only options
34
34
  */
35
35
  interface InternalExecAgentParams extends ExecAgentParams {
36
+ /** Cron job ID that triggered this execution (if trigger is 'cron') */
37
+ cronJobId?: string;
36
38
  /** Step lifecycle callbacks for operation tracking (server-side only) */
37
39
  stepCallbacks?: StepLifecycleCallbacks;
40
+ /** Topic creation trigger source ('cron' | 'chat' | 'api') */
41
+ trigger?: string;
38
42
  }
39
43
 
40
44
  /**
@@ -88,6 +92,8 @@ export class AiAgentService {
88
92
  autoStart = true,
89
93
  existingMessageIds = [],
90
94
  stepCallbacks,
95
+ trigger,
96
+ cronJobId,
91
97
  } = params;
92
98
 
93
99
  // Validate that either agentId or slug is provided
@@ -114,12 +120,22 @@ export class AiAgentService {
114
120
  // 2. Handle topic creation: if no topicId provided, create a new topic; otherwise reuse existing
115
121
  let topicId = appContext?.topicId;
116
122
  if (!topicId) {
123
+ // Prepare metadata with cronJobId if provided
124
+ const metadata = cronJobId ? { cronJobId } : undefined;
125
+
117
126
  const newTopic = await this.topicModel.create({
118
127
  agentId: resolvedAgentId,
128
+ metadata,
119
129
  title: prompt.slice(0, 50) + (prompt.length > 50 ? '...' : ''),
130
+ trigger,
120
131
  });
121
132
  topicId = newTopic.id;
122
- log('execAgent: created new topic %s', topicId);
133
+ log(
134
+ 'execAgent: created new topic %s with trigger %s, cronJobId %s',
135
+ topicId,
136
+ trigger || 'default',
137
+ cronJobId || 'none',
138
+ );
123
139
  } else {
124
140
  log('execAgent: reusing existing topic %s', topicId);
125
141
  }
@@ -397,6 +413,7 @@ export class AiAgentService {
397
413
  groupId,
398
414
  messages: newTopic?.topicMessageIds,
399
415
  title: topicTitle,
416
+ // Note: execGroupAgent doesn't have trigger param yet, defaults to null
400
417
  });
401
418
  topicId = topicItem.id;
402
419
  isCreateNewTopic = true;
@@ -25,13 +25,15 @@ import {
25
25
  type DiscoverProviderItem,
26
26
  type DiscoverUserProfile,
27
27
  type IdentifiersResponse,
28
+ McpCategory,
28
29
  type McpListResponse,
29
30
  type McpQueryParams,
31
+ McpSorts,
30
32
  type ModelListResponse,
31
33
  type ModelQueryParams,
32
34
  ModelSorts,
33
35
  type PluginListResponse,
34
- type PluginQueryParams,
36
+ type PluginQueryParams as PluginQueryParams,
35
37
  PluginSorts,
36
38
  type ProviderListResponse,
37
39
  type ProviderQueryParams,
@@ -48,7 +50,12 @@ import {
48
50
  MarketSDK,
49
51
  type UserInfoResponse,
50
52
  } from '@lobehub/market-sdk';
51
- import { type CallReportRequest, type InstallReportRequest } from '@lobehub/market-types';
53
+ import {
54
+ AgentEventRequest,
55
+ type CallReportRequest,
56
+ type InstallReportRequest,
57
+ type PluginEventRequest,
58
+ } from '@lobehub/market-types';
52
59
  import dayjs from 'dayjs';
53
60
  import debug from 'debug';
54
61
  import { cloneDeep, countBy, isString, merge, uniq, uniqBy } from 'es-toolkit/compat';
@@ -850,12 +857,16 @@ export class DiscoverService {
850
857
 
851
858
  getMcpList = async (params: McpQueryParams = {}): Promise<McpListResponse> => {
852
859
  log('getMcpList: params=%O', params);
853
- const { locale } = params;
860
+ const { category, locale, sort } = params;
854
861
  const normalizedLocale = normalizeLocale(locale);
862
+ const isDiscoverCategory = category === McpCategory.Discover;
863
+
855
864
  const result = await this.market.plugins.getPluginList(
856
865
  {
857
866
  ...params,
867
+ category: isDiscoverCategory ? undefined : category,
858
868
  locale: normalizedLocale,
869
+ sort: isDiscoverCategory ? McpSorts.Recommended : sort,
859
870
  },
860
871
  {
861
872
  next: {
@@ -897,6 +908,21 @@ export class DiscoverService {
897
908
  await this.market.plugins.reportInstallation(params);
898
909
  };
899
910
 
911
+ /**
912
+ * record Agent plugin event
913
+ */
914
+ createAgentEvent = async (params: AgentEventRequest) => {
915
+ await this.market.agents.createEvent(params);
916
+ };
917
+
918
+ /**
919
+ * record MCP plugin event
920
+ */
921
+ createPluginEvent = async (params: PluginEventRequest) => {
922
+ await this.market.plugins.createEvent(params);
923
+ };
924
+
925
+
900
926
  /**
901
927
  * report plugin call result to marketplace
902
928
  */