@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
@@ -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();
@@ -1,5 +1,10 @@
1
1
  import { type CategoryItem, type CategoryListQuery, type PluginManifest } from '@lobehub/market-sdk';
2
- import { type CallReportRequest, type InstallReportRequest } from '@lobehub/market-types';
2
+ import {
3
+ AgentEventRequest,
4
+ type CallReportRequest,
5
+ type InstallReportRequest,
6
+ type PluginEventRequest,
7
+ } from '@lobehub/market-types';
3
8
 
4
9
  import { lambdaClient } from '@/libs/trpc/client';
5
10
  import { globalHelpers } from '@/store/global/helpers';
@@ -195,6 +200,22 @@ class DiscoverService {
195
200
  });
196
201
  };
197
202
 
203
+ reportMcpEvent = async (eventData: PluginEventRequest) => {
204
+ const allow = userGeneralSettingsSelectors.telemetry(useUserStore.getState());
205
+ if (!allow) return;
206
+
207
+ await this.injectMPToken();
208
+
209
+ const payload = cleanObject({
210
+ ...eventData,
211
+ source: eventData.source ?? 'community/mcp',
212
+ });
213
+
214
+ lambdaClient.market.reportMcpEvent.mutate(payload).catch((error) => {
215
+ console.warn('Failed to report MCP event:', error);
216
+ });
217
+ };
218
+
198
219
  /**
199
220
  * Report agent installation to increase install count
200
221
  */
@@ -211,6 +232,22 @@ class DiscoverService {
211
232
  });
212
233
  };
213
234
 
235
+ reportAgentEvent = async (eventData: AgentEventRequest) => {
236
+ const allow = userGeneralSettingsSelectors.telemetry(useUserStore.getState());
237
+ if (!allow) return;
238
+
239
+ await this.injectMPToken();
240
+
241
+ const payload = cleanObject({
242
+ ...eventData,
243
+ source: eventData.source ?? 'community/agent',
244
+ });
245
+
246
+ lambdaClient.market.reportAgentEvent.mutate(payload).catch((error) => {
247
+ console.warn('Failed to report Agent event:', error);
248
+ });
249
+ };
250
+
214
251
  // ============================== Models ==============================
215
252
 
216
253
  getModelCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
@@ -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,
@@ -38,6 +38,11 @@ const n = setNamespace('t');
38
38
 
39
39
  const SWR_USE_FETCH_TOPIC = 'SWR_USE_FETCH_TOPIC';
40
40
  const SWR_USE_SEARCH_TOPIC = 'SWR_USE_SEARCH_TOPIC';
41
+ type CronTopicsGroupWithJobInfo = {
42
+ cronJob: unknown;
43
+ cronJobId: string;
44
+ topics: ChatTopic[];
45
+ };
41
46
 
42
47
  /**
43
48
  * Options for switchTopic action
@@ -91,6 +96,7 @@ export interface ChatTopicAction {
91
96
  enable: boolean,
92
97
  params: {
93
98
  agentId?: string;
99
+ excludeTriggers?: string[];
94
100
  groupId?: string;
95
101
  isInbox?: boolean;
96
102
  pageSize?: number;
@@ -282,7 +288,34 @@ export const chatTopic: StateCreator<
282
288
  });
283
289
  },
284
290
  favoriteTopic: async (id, favorite) => {
291
+ const { activeAgentId } = get();
285
292
  await get().internal_updateTopic(id, { favorite });
293
+
294
+ if (!activeAgentId) return;
295
+
296
+ await mutate(
297
+ ['cronTopicsWithJobInfo', activeAgentId],
298
+ (groups?: CronTopicsGroupWithJobInfo[]) => {
299
+ if (!groups) return groups;
300
+
301
+ let updated = false;
302
+ const next = groups.map((group) => {
303
+ let groupUpdated = false;
304
+ const topics = group.topics.map((topic) => {
305
+ if (topic.id !== id) return topic;
306
+ if (topic.favorite === favorite) return topic;
307
+ groupUpdated = true;
308
+ updated = true;
309
+ return { ...topic, favorite };
310
+ });
311
+
312
+ return groupUpdated ? { ...group, topics } : group;
313
+ });
314
+
315
+ return updated ? next : groups;
316
+ },
317
+ { revalidate: false },
318
+ );
286
319
  },
287
320
 
288
321
  updateTopicMetadata: async (id, metadata) => {
@@ -314,15 +347,28 @@ export const chatTopic: StateCreator<
314
347
  },
315
348
 
316
349
  // query
317
- useFetchTopics: (enable, { agentId, groupId, pageSize: customPageSize, isInbox }) => {
350
+ useFetchTopics: (
351
+ enable,
352
+ { agentId, excludeTriggers, groupId, pageSize: customPageSize, isInbox },
353
+ ) => {
318
354
  const pageSize = customPageSize || 20;
355
+ const effectiveExcludeTriggers =
356
+ excludeTriggers && excludeTriggers.length > 0 ? excludeTriggers : undefined;
319
357
  // Use topicMapKey to generate the container key for topic data map
320
358
  const containerKey = topicMapKey({ agentId, groupId });
321
359
  const hasValidContainer = !!(groupId || agentId);
322
360
 
323
361
  return useClientDataSWRWithSync<{ items: ChatTopic[]; total: number }>(
324
362
  enable && hasValidContainer
325
- ? [SWR_USE_FETCH_TOPIC, containerKey, { isInbox, pageSize }]
363
+ ? [
364
+ SWR_USE_FETCH_TOPIC,
365
+ containerKey,
366
+ {
367
+ isInbox,
368
+ pageSize,
369
+ ...(effectiveExcludeTriggers ? { excludeTriggers: effectiveExcludeTriggers } : {}),
370
+ },
371
+ ]
326
372
  : null,
327
373
  async () => {
328
374
  // agentId, groupId, isInbox, pageSize come from the outer scope closure
@@ -343,6 +389,7 @@ export const chatTopic: StateCreator<
343
389
  const result = await topicService.getTopics({
344
390
  agentId,
345
391
  current: 0,
392
+ excludeTriggers: effectiveExcludeTriggers,
346
393
  groupId,
347
394
  isInbox,
348
395
  pageSize,
@@ -374,6 +421,7 @@ export const chatTopic: StateCreator<
374
421
  ...get().topicDataMap,
375
422
  [containerKey]: {
376
423
  currentPage: 0,
424
+ excludeTriggers: effectiveExcludeTriggers,
377
425
  hasMore,
378
426
  isExpandingPageSize: false,
379
427
  items: topics,
@@ -413,9 +461,11 @@ export const chatTopic: StateCreator<
413
461
 
414
462
  try {
415
463
  const pageSize = useGlobalStore.getState().status.topicPageSize || 20;
464
+ const excludeTriggers = currentData?.excludeTriggers;
416
465
  const result = await topicService.getTopics({
417
466
  agentId: activeAgentId,
418
467
  current: nextPage,
468
+ excludeTriggers,
419
469
  groupId: activeGroupId,
420
470
  pageSize,
421
471
  });
@@ -429,6 +479,7 @@ export const chatTopic: StateCreator<
429
479
  ...get().topicDataMap,
430
480
  [key]: {
431
481
  currentPage: nextPage,
482
+ excludeTriggers,
432
483
  hasMore,
433
484
  isLoadingMore: false,
434
485
  items: [...currentTopics, ...result.items],
@@ -5,6 +5,7 @@ import { type ChatTopic } from '@/types/topic';
5
5
  */
6
6
  export interface TopicData {
7
7
  currentPage: number;
8
+ excludeTriggers?: string[];
8
9
  hasMore: boolean;
9
10
  isExpandingPageSize?: boolean;
10
11
  isLoadingMore?: boolean;
@@ -18,27 +18,34 @@ const currentTopicData = (s: ChatStoreState): TopicData | undefined => {
18
18
 
19
19
  const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => currentTopicData(s)?.items;
20
20
 
21
+ // Get topics without cron-triggered ones
22
+ const currentTopicsWithoutCron = (s: ChatStoreState): ChatTopic[] | undefined => {
23
+ const topics = currentTopics(s);
24
+ if (!topics) return undefined;
25
+ return topics.filter((topic) => topic.trigger !== 'cron');
26
+ };
27
+
21
28
  const currentActiveTopic = (s: ChatStoreState): ChatTopic | undefined => {
22
29
  return currentTopics(s)?.find((topic) => topic.id === s.activeTopicId);
23
30
  };
24
31
  const searchTopics = (s: ChatStoreState): ChatTopic[] => s.searchTopics;
25
32
 
26
- const displayTopics = (s: ChatStoreState): ChatTopic[] | undefined => currentTopics(s);
33
+ const displayTopics = (s: ChatStoreState): ChatTopic[] | undefined => currentTopicsWithoutCron(s);
27
34
 
28
35
  const currentFavTopics = (s: ChatStoreState): ChatTopic[] =>
29
- currentTopics(s)?.filter((s) => s.favorite) || [];
36
+ currentTopicsWithoutCron(s)?.filter((s) => s.favorite) || [];
30
37
 
31
38
  const currentUnFavTopics = (s: ChatStoreState): ChatTopic[] =>
32
- currentTopics(s)?.filter((s) => !s.favorite) || [];
39
+ currentTopicsWithoutCron(s)?.filter((s) => !s.favorite) || [];
33
40
 
34
- const currentTopicLength = (s: ChatStoreState): number => currentTopicData(s)?.items?.length || 0;
41
+ const currentTopicLength = (s: ChatStoreState): number => currentTopicsWithoutCron(s)?.length || 0;
35
42
 
36
43
  const currentTopicCount = (s: ChatStoreState): number => currentTopicData(s)?.total || 0;
37
44
 
38
45
  const getTopicById =
39
46
  (id: string) =>
40
47
  (s: ChatStoreState): ChatTopic | undefined =>
41
- currentTopics(s)?.find((topic) => topic.id === id);
48
+ currentTopics(s)?.find((topic) => topic.id === id); // Don't filter here, need to access all topics by ID
42
49
 
43
50
  /**
44
51
  * Get topics by specific agentId (for AgentBuilder scenarios where agentId differs from activeAgentId)
@@ -79,7 +86,7 @@ const isSearchingTopic = (s: ChatStoreState) => s.isSearchingTopic;
79
86
  const displayTopicsForSidebar =
80
87
  (pageSize: number) =>
81
88
  (s: ChatStoreState): ChatTopic[] | undefined => {
82
- const topics = currentTopics(s);
89
+ const topics = currentTopicsWithoutCron(s);
83
90
  if (!topics) return undefined;
84
91
 
85
92
  // Return only the first page worth of topics for sidebar
@@ -143,6 +150,7 @@ export const topicSelectors = {
143
150
  currentTopicLength,
144
151
  currentTopicWorkingDirectory,
145
152
  currentTopics,
153
+ currentTopicsWithoutCron,
146
154
  currentUnFavTopics,
147
155
  displayTopics,
148
156
  displayTopicsForSidebar,
@@ -12,6 +12,37 @@ import { CheckMcpInstallResult, MCPInstallStep } from '@/types/plugins';
12
12
 
13
13
  import { useToolStore } from '../../store';
14
14
 
15
+ vi.mock('@/libs/trpc/client', () => ({
16
+ asyncClient: {},
17
+ lambdaClient: {
18
+ market: {
19
+ getMcpCategories: { query: vi.fn() },
20
+ getMcpDetail: { query: vi.fn() },
21
+ getMcpList: { query: vi.fn() },
22
+ getMcpManifest: { query: vi.fn() },
23
+ registerClientInMarketplace: {
24
+ mutate: vi.fn().mockResolvedValue({
25
+ clientId: 'test-client-id',
26
+ clientSecret: 'test-client-secret',
27
+ }),
28
+ },
29
+ registerM2MToken: { query: vi.fn().mockResolvedValue({ success: true }) },
30
+ reportCall: { mutate: vi.fn().mockResolvedValue(undefined) },
31
+ reportMcpEvent: { mutate: vi.fn().mockResolvedValue(undefined) },
32
+ reportMcpInstallResult: { mutate: vi.fn().mockResolvedValue(undefined) },
33
+ },
34
+ },
35
+ toolsClient: {
36
+ market: {
37
+ callCloudMcpEndpoint: { mutate: vi.fn() },
38
+ },
39
+ mcp: {
40
+ callTool: { mutate: vi.fn() },
41
+ getStreamableMcpServerManifest: { query: vi.fn() },
42
+ },
43
+ },
44
+ }));
45
+
15
46
  // Keep zustand mock as it's needed globally
16
47
  vi.mock('zustand/traditional');
17
48
 
@@ -61,6 +92,13 @@ const bootstrapToolStoreWithDesktop = async (isDesktopEnv: boolean) => {
61
92
  beforeEach(() => {
62
93
  vi.clearAllMocks();
63
94
 
95
+ vi.spyOn(discoverService, 'injectMPToken').mockResolvedValue(undefined);
96
+ vi.spyOn(discoverService, 'registerClient').mockResolvedValue({
97
+ clientId: 'test-client-id',
98
+ clientSecret: 'test-client-secret',
99
+ });
100
+ vi.spyOn(discoverService, 'reportMcpEvent').mockResolvedValue(undefined as any);
101
+
64
102
  // Reset store state
65
103
  act(() => {
66
104
  useToolStore.setState(
@@ -588,6 +588,12 @@ export const createMCPPluginStoreSlice: StateCreator<
588
588
  // Calculate installation duration
589
589
  const installDurationMs = Date.now() - installStartTime;
590
590
 
591
+ discoverService.reportMcpEvent({
592
+ event: 'install',
593
+ identifier: plugin.identifier,
594
+ source: 'self',
595
+ })
596
+
591
597
  discoverService.reportMcpInstallResult({
592
598
  identifier: plugin.identifier,
593
599
  installDurationMs,
@@ -790,6 +796,12 @@ export const createMCPPluginStoreSlice: StateCreator<
790
796
  n('testMcpConnection/success'),
791
797
  );
792
798
 
799
+ discoverService.reportMcpEvent({
800
+ event: 'activate',
801
+ identifier: identifier,
802
+ source: 'self',
803
+ })
804
+
793
805
  return { manifest, success: true };
794
806
  } catch (error) {
795
807
  // Silently handle errors caused by cancellation
@@ -817,6 +829,12 @@ export const createMCPPluginStoreSlice: StateCreator<
817
829
  uninstallMCPPlugin: async (identifier) => {
818
830
  await pluginService.uninstallPlugin(identifier);
819
831
  await get().refreshPlugins();
832
+
833
+ discoverService.reportMcpEvent({
834
+ event: 'uninstall',
835
+ identifier: identifier,
836
+ source: 'self',
837
+ })
820
838
  },
821
839
 
822
840
  updateMCPInstallProgress: (identifier, progress) => {
@@ -4,10 +4,7 @@ import {
4
4
  LocalSystemListFilesPlaceholder,
5
5
  LocalSystemSearchFilesPlaceholder,
6
6
  } from '@lobechat/builtin-tool-local-system/client';
7
- import {
8
- NotebookIdentifier,
9
- NotebookPlaceholders,
10
- } from '@lobechat/builtin-tool-notebook/client';
7
+ import { NotebookIdentifier, NotebookPlaceholders } from '@lobechat/builtin-tool-notebook/client';
11
8
  import {
12
9
  WebBrowsingManifest,
13
10
  WebBrowsingPlaceholders,