@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
- package/packages/database/src/models/topic.ts +71 -4
- package/packages/database/src/schemas/agentCronJob.ts +1 -2
- package/packages/memory-user-memory/src/extractors/context.ts +1 -4
- package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
- package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
- package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
- package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
- package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
- package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
- package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
- package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
- package/packages/memory-user-memory/src/types.ts +10 -8
- package/packages/types/src/discover/mcp.ts +1 -1
- package/packages/types/src/topic/topic.ts +9 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
- package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
- package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +6 -0
- package/src/app/[variants]/(main)/community/(list)/assistant/features/List/Item.tsx +12 -3
- package/src/app/[variants]/(main)/community/(list)/mcp/features/List/Item.tsx +14 -4
- package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
- package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +1 -1
- package/src/hooks/useFetchCronTopics.ts +29 -0
- package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
- package/src/hooks/useFetchTopics.ts +4 -1
- package/src/locales/default/setting.ts +44 -1
- package/src/server/routers/lambda/agentCronJob.ts +367 -0
- package/src/server/routers/lambda/image/index.test.ts +2 -2
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/market/index.ts +45 -4
- package/src/server/routers/lambda/topic.ts +15 -3
- package/src/server/services/aiAgent/index.ts +18 -1
- package/src/server/services/discover/index.ts +29 -3
- package/src/server/services/memory/userMemory/extract.ts +14 -6
- package/src/services/agentCronJob.ts +95 -0
- package/src/services/discover.ts +38 -1
- package/src/services/topic/index.ts +1 -0
- package/src/store/chat/slices/topic/action.ts +53 -2
- package/src/store/chat/slices/topic/initialState.ts +1 -0
- package/src/store/chat/slices/topic/selectors.ts +14 -6
- package/src/store/tool/slices/mcpStore/action.test.ts +38 -0
- package/src/store/tool/slices/mcpStore/action.ts +18 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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(
|
|
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 {
|
|
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
|
*/
|