@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/apps/desktop/build/entitlements.mac.plist +9 -0
  3. package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
  4. package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
  5. package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
  6. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
  7. package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
  8. package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
  9. package/apps/desktop/src/main/locales/default/menu.ts +7 -0
  10. package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
  11. package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -0
  12. package/changelog/v1.json +14 -0
  13. package/package.json +1 -1
  14. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
  15. package/packages/database/migrations/meta/_journal.json +1 -1
  16. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
  17. package/packages/database/src/models/topic.ts +71 -4
  18. package/packages/database/src/schemas/agentCronJob.ts +1 -2
  19. package/packages/electron-client-ipc/src/events/system.ts +1 -0
  20. package/packages/memory-user-memory/src/extractors/context.ts +1 -4
  21. package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
  22. package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
  23. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
  24. package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
  25. package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
  26. package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
  27. package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
  28. package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
  29. package/packages/memory-user-memory/src/types.ts +10 -8
  30. package/packages/types/src/topic/topic.ts +9 -0
  31. package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
  32. package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
  33. package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -0
  34. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
  35. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
  36. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
  37. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
  38. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
  39. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
  40. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
  41. package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
  42. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
  43. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
  44. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
  45. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
  46. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
  47. package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
  48. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  49. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
  50. package/src/hooks/useFetchCronTopics.ts +29 -0
  51. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
  52. package/src/hooks/useFetchTopics.ts +4 -1
  53. package/src/locales/default/setting.ts +44 -1
  54. package/src/server/routers/lambda/agentCronJob.ts +367 -0
  55. package/src/server/routers/lambda/image/index.test.ts +2 -2
  56. package/src/server/routers/lambda/index.ts +2 -0
  57. package/src/server/routers/lambda/topic.ts +15 -3
  58. package/src/server/services/aiAgent/index.ts +18 -1
  59. package/src/server/services/memory/userMemory/extract.ts +14 -6
  60. package/src/services/agentCronJob.ts +95 -0
  61. package/src/services/topic/index.ts +1 -0
  62. package/src/store/chat/slices/topic/action.ts +53 -2
  63. package/src/store/chat/slices/topic/initialState.ts +1 -0
  64. package/src/store/chat/slices/topic/selectors.ts +14 -6
  65. package/src/tools/placeholders.ts +1 -4
  66. 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({ ...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;
@@ -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 { LayersEnum, type MergeStrategyEnum, TypesEnum, MemorySourceType } from '@/types/userMemory';
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(job: MemoryExtractionJob, userId: string): Promise<IdentityMemoryDetail[]> {
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();
@@ -37,6 +37,7 @@ export class TopicService {
37
37
  return lambdaClient.topic.getTopics.query({
38
38
  agentId: params.agentId,
39
39
  current: params.current,
40
+ excludeTriggers: params.excludeTriggers,
40
41
  groupId: params.groupId,
41
42
  isInbox: params.isInbox,
42
43
  pageSize: params.pageSize,