@lobehub/lobehub 2.0.0-next.251 → 2.0.0-next.253
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 +58 -0
- package/apps/desktop/build/entitlements.mac.plist +9 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
- package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
- package/apps/desktop/src/main/locales/default/menu.ts +7 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
- package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -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/electron-client-ipc/src/events/system.ts +1 -0
- 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/topic/topic.ts +9 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
- package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -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]/router/desktopRouter.config.tsx +7 -0
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
- 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/topic.ts +15 -3
- package/src/server/services/aiAgent/index.ts +18 -1
- package/src/server/services/memory/userMemory/extract.ts +14 -6
- package/src/services/agentCronJob.ts +95 -0
- 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/tools/placeholders.ts +1 -4
- package/apps/desktop/src/main/controllers/scripts/full-disk-access.applescript +0 -85
|
@@ -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,
|
|
@@ -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;
|
|
@@ -4,21 +4,20 @@ import {
|
|
|
4
4
|
} from '@lobechat/const';
|
|
5
5
|
import { messages, topics } from '@lobechat/database/schemas';
|
|
6
6
|
import {
|
|
7
|
+
BenchmarkLocomoContextProvider,
|
|
7
8
|
LobeChatTopicContextProvider,
|
|
8
9
|
LobeChatTopicResultRecorder,
|
|
9
10
|
MemoryExtractionService,
|
|
10
11
|
RetrievalUserMemoryContextProvider,
|
|
11
12
|
RetrievalUserMemoryIdentitiesProvider,
|
|
12
|
-
BenchmarkLocomoContextProvider,
|
|
13
13
|
} from '@lobechat/memory-user-memory';
|
|
14
14
|
import type {
|
|
15
|
+
BenchmarkLocomoPart,
|
|
15
16
|
MemoryExtractionAgent,
|
|
16
17
|
MemoryExtractionJob,
|
|
17
18
|
MemoryExtractionResult,
|
|
18
|
-
BenchmarkLocomoPart,
|
|
19
19
|
PersistedMemoryResult,
|
|
20
20
|
} from '@lobechat/memory-user-memory';
|
|
21
|
-
import { UserMemorySourceBenchmarkLoCoMoModel } from '@/database/models/userMemory/sources/benchmarkLoCoMo';
|
|
22
21
|
import { ModelRuntime } from '@lobechat/model-runtime';
|
|
23
22
|
import type {
|
|
24
23
|
Embeddings,
|
|
@@ -45,6 +44,7 @@ import type {
|
|
|
45
44
|
MemoryExtractionTracePayload,
|
|
46
45
|
} from '@lobechat/types';
|
|
47
46
|
import { Client } from '@upstash/workflow';
|
|
47
|
+
import debug from 'debug';
|
|
48
48
|
import { and, asc, eq, inArray } from 'drizzle-orm';
|
|
49
49
|
import { join } from 'pathe';
|
|
50
50
|
import { z } from 'zod';
|
|
@@ -54,6 +54,7 @@ import { TopicModel } from '@/database/models/topic';
|
|
|
54
54
|
import type { ListUsersForMemoryExtractorCursor } from '@/database/models/user';
|
|
55
55
|
import { UserModel } from '@/database/models/user';
|
|
56
56
|
import { UserMemoryModel } from '@/database/models/userMemory';
|
|
57
|
+
import { UserMemorySourceBenchmarkLoCoMoModel } from '@/database/models/userMemory/sources/benchmarkLoCoMo';
|
|
57
58
|
import { getServerDB } from '@/database/server';
|
|
58
59
|
import { getServerGlobalConfig } from '@/server/globalConfig';
|
|
59
60
|
import {
|
|
@@ -64,9 +65,13 @@ import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
|
|
64
65
|
import { S3 } from '@/server/modules/S3';
|
|
65
66
|
import type { GlobalMemoryLayer } from '@/types/serverConfig';
|
|
66
67
|
import type { UserKeyVaults } from '@/types/user/settings';
|
|
67
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
LayersEnum,
|
|
70
|
+
MemorySourceType,
|
|
71
|
+
type MergeStrategyEnum,
|
|
72
|
+
TypesEnum,
|
|
73
|
+
} from '@/types/userMemory';
|
|
68
74
|
import { encodeAsync } from '@/utils/tokenizer';
|
|
69
|
-
import debug from 'debug';
|
|
70
75
|
|
|
71
76
|
const SOURCE_ALIAS_MAP: Record<string, MemorySourceType> = {
|
|
72
77
|
benchmark_locomo: MemorySourceType.BenchmarkLocomo,
|
|
@@ -913,7 +918,10 @@ export class MemoryExtractionExecutor {
|
|
|
913
918
|
};
|
|
914
919
|
}
|
|
915
920
|
|
|
916
|
-
async listUserMemoryIdentities(
|
|
921
|
+
async listUserMemoryIdentities(
|
|
922
|
+
job: MemoryExtractionJob,
|
|
923
|
+
userId: string,
|
|
924
|
+
): Promise<IdentityMemoryDetail[]> {
|
|
917
925
|
const db = await this.db;
|
|
918
926
|
const userMemoryModel = new UserMemoryModel(db, userId);
|
|
919
927
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateAgentCronJobData,
|
|
3
|
+
UpdateAgentCronJobData,
|
|
4
|
+
} from '@/database/schemas/agentCronJob';
|
|
5
|
+
import { lambdaClient } from '@/libs/trpc/client/lambda';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side service for Agent Cron Job operations
|
|
9
|
+
*
|
|
10
|
+
* This service provides a clean interface for frontend components
|
|
11
|
+
* to interact with agent cron job data using tRPC client.
|
|
12
|
+
*/
|
|
13
|
+
class AgentCronJobService {
|
|
14
|
+
/**
|
|
15
|
+
* Create a new cron job
|
|
16
|
+
*/
|
|
17
|
+
async create(data: Omit<CreateAgentCronJobData, 'userId'>) {
|
|
18
|
+
return await lambdaClient.agentCronJob.create.mutate(data);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get cron jobs for a specific agent
|
|
23
|
+
*/
|
|
24
|
+
async getByAgentId(agentId: string) {
|
|
25
|
+
return await lambdaClient.agentCronJob.findByAgent.query({ agentId });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get a single cron job by ID
|
|
30
|
+
*/
|
|
31
|
+
async getById(id: string) {
|
|
32
|
+
return await lambdaClient.agentCronJob.findById.query({ id });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* List cron jobs with pagination and filtering
|
|
37
|
+
*/
|
|
38
|
+
async list(
|
|
39
|
+
options: {
|
|
40
|
+
agentId?: string;
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
limit?: number;
|
|
43
|
+
offset?: number;
|
|
44
|
+
} = {},
|
|
45
|
+
) {
|
|
46
|
+
return await lambdaClient.agentCronJob.list.query(options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update a cron job
|
|
51
|
+
*/
|
|
52
|
+
async update(id: string, data: UpdateAgentCronJobData) {
|
|
53
|
+
return await lambdaClient.agentCronJob.update.mutate({ data, id });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Delete a cron job
|
|
58
|
+
*/
|
|
59
|
+
async delete(id: string) {
|
|
60
|
+
return await lambdaClient.agentCronJob.delete.mutate({ id });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reset execution counts
|
|
65
|
+
*/
|
|
66
|
+
async resetExecutions(id: string, newMaxExecutions?: number) {
|
|
67
|
+
return await lambdaClient.agentCronJob.resetExecutions.mutate({
|
|
68
|
+
id,
|
|
69
|
+
newMaxExecutions,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get execution statistics
|
|
75
|
+
*/
|
|
76
|
+
async getStats() {
|
|
77
|
+
return await lambdaClient.agentCronJob.getStats.query();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get jobs near depletion
|
|
82
|
+
*/
|
|
83
|
+
async getNearDepletion(threshold: number = 5) {
|
|
84
|
+
return await lambdaClient.agentCronJob.getNearDepletion.query({ threshold });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Batch update status (enable/disable) for multiple jobs
|
|
89
|
+
*/
|
|
90
|
+
async batchUpdateStatus(ids: string[], enabled: boolean) {
|
|
91
|
+
return await lambdaClient.agentCronJob.batchUpdateStatus.mutate({ enabled, ids });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const agentCronJobService = new AgentCronJobService();
|