@lobehub/lobehub 2.0.0-next.344 → 2.0.0-next.346

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 (185) hide show
  1. package/.cursor/rules/i18n.mdc +1 -1
  2. package/.cursor/rules/modal-imperative.mdc +162 -0
  3. package/.cursor/rules/rules-index.mdc +1 -0
  4. package/.env.example +0 -14
  5. package/.eslintrc.js +8 -1
  6. package/CHANGELOG.md +66 -0
  7. package/CLAUDE.md +4 -2
  8. package/Dockerfile +3 -13
  9. package/README.md +3 -5
  10. package/README.zh-CN.md +3 -5
  11. package/changelog/v1.json +20 -0
  12. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
  13. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
  14. package/e2e/src/support/webServer.ts +2 -0
  15. package/locales/ar/error.json +0 -4
  16. package/locales/bg-BG/error.json +0 -4
  17. package/locales/de-DE/error.json +0 -4
  18. package/locales/en-US/error.json +0 -4
  19. package/locales/es-ES/error.json +0 -4
  20. package/locales/fa-IR/error.json +0 -4
  21. package/locales/fr-FR/error.json +0 -4
  22. package/locales/it-IT/error.json +0 -4
  23. package/locales/ja-JP/error.json +0 -4
  24. package/locales/ko-KR/error.json +0 -4
  25. package/locales/nl-NL/error.json +0 -4
  26. package/locales/pl-PL/error.json +0 -4
  27. package/locales/pt-BR/error.json +0 -4
  28. package/locales/ru-RU/error.json +0 -4
  29. package/locales/tr-TR/error.json +0 -4
  30. package/locales/vi-VN/error.json +0 -4
  31. package/locales/zh-CN/error.json +0 -4
  32. package/locales/zh-TW/error.json +0 -4
  33. package/package.json +12 -12
  34. package/packages/builtin-agents/package.json +2 -0
  35. package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
  36. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
  37. package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
  38. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
  39. package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
  40. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
  41. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
  42. package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
  43. package/packages/file-loaders/package.json +1 -1
  44. package/packages/file-loaders/src/loadFile.ts +10 -15
  45. package/packages/file-loaders/src/loaders/index.ts +68 -19
  46. package/packages/file-loaders/src/loaders/pdf/__snapshots__/index.test.ts.snap +1 -1
  47. package/packages/file-loaders/test/__snapshots__/loaders.test.ts.snap +1 -1
  48. package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
  49. package/packages/model-bank/src/modelProviders/fal.ts +0 -1
  50. package/packages/types/src/fetch.ts +1 -2
  51. package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
  52. package/packages/utils/src/server/auth.ts +1 -9
  53. package/pnpm-workspace.yaml +1 -0
  54. package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
  55. package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
  56. package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
  57. package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
  58. package/scripts/countEnWord.ts +1 -1
  59. package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
  60. package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
  61. package/scripts/prebuild.mts +10 -8
  62. package/scripts/serverLauncher/startServer.js +23 -5
  63. package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
  64. package/src/app/(backend)/middleware/auth/index.ts +0 -15
  65. package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
  66. package/src/app/(backend)/middleware/auth/utils.ts +2 -17
  67. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
  68. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
  69. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
  70. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
  71. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
  72. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
  73. package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
  74. package/src/app/[variants]/(main)/resource/features/store/action.ts +2 -2
  75. package/src/app/[variants]/(main)/resource/features/store/initialState.ts +2 -2
  76. package/src/app/[variants]/(main)/resource/store/action.ts +2 -2
  77. package/src/app/[variants]/(main)/resource/store/initialState.ts +2 -2
  78. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
  79. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
  80. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
  81. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
  82. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
  83. package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
  84. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
  85. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
  86. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
  87. package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
  88. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
  89. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  90. package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
  91. package/src/app/robots.tsx +1 -1
  92. package/src/envs/auth.ts +2 -27
  93. package/src/envs/llm.ts +2 -2
  94. package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
  95. package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
  96. package/src/features/ChatMiniMap/utils.ts +1 -1
  97. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  98. package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
  99. package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
  100. package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
  101. package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
  102. package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
  103. package/src/features/FileViewer/Renderer/PDF/index.tsx +5 -8
  104. package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
  105. package/src/features/IntegrationDetailModal/index.tsx +21 -283
  106. package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
  107. package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
  108. package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
  109. package/src/features/ProfileEditor/AgentTool.tsx +14 -20
  110. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +0 -8
  111. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
  112. package/src/features/ResourceManager/index.tsx +1 -1
  113. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +4 -4
  114. package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
  115. package/src/features/SkillStore/Search/index.tsx +1 -1
  116. package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
  117. package/src/features/SkillStore/index.tsx +15 -33
  118. package/src/features/User/UserPanel/PanelContent.tsx +0 -8
  119. package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
  120. package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
  121. package/src/features/User/__tests__/useMenu.test.tsx +2 -43
  122. package/src/layout/AuthProvider/index.tsx +0 -5
  123. package/src/libs/next/config/define-config.ts +20 -15
  124. package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
  125. package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
  126. package/src/libs/next/proxy/define-config.ts +4 -53
  127. package/src/libs/next-auth/adapter/index.ts +1 -2
  128. package/src/libs/oidc-provider/provider.test.ts +5 -316
  129. package/src/libs/pdfjs/pdf.worker.ts +1 -0
  130. package/src/libs/pdfjs/worker.ts +12 -0
  131. package/src/libs/trpc/lambda/context.test.ts +0 -13
  132. package/src/libs/trpc/lambda/context.ts +3 -22
  133. package/src/libs/trpc/middleware/userAuth.ts +2 -4
  134. package/src/libs/trusted-client/getSessionUser.ts +2 -17
  135. package/src/locales/default/error.ts +0 -6
  136. package/src/locales/default/index.ts +0 -2
  137. package/src/proxy.ts +0 -1
  138. package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
  139. package/src/server/routers/lambda/user.ts +6 -63
  140. package/src/server/services/changelog/index.test.ts +3 -2
  141. package/src/server/services/changelog/index.ts +1 -1
  142. package/src/server/services/user/index.ts +0 -83
  143. package/src/services/chat/index.ts +1 -2
  144. package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
  145. package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
  146. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
  147. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
  148. package/src/store/user/slices/auth/action.test.ts +1 -81
  149. package/src/store/user/slices/auth/action.ts +3 -28
  150. package/src/store/user/slices/auth/initialState.ts +1 -18
  151. package/src/store/user/slices/auth/selectors.test.ts +2 -127
  152. package/src/store/user/slices/auth/selectors.ts +1 -21
  153. package/src/utils/errorResponse.ts +1 -4
  154. package/src/utils/markdownToTxt.ts +20 -0
  155. package/locales/ar/clerk.json +0 -545
  156. package/locales/bg-BG/clerk.json +0 -545
  157. package/locales/de-DE/clerk.json +0 -545
  158. package/locales/en-US/clerk.json +0 -545
  159. package/locales/es-ES/clerk.json +0 -545
  160. package/locales/fa-IR/clerk.json +0 -545
  161. package/locales/fr-FR/clerk.json +0 -545
  162. package/locales/it-IT/clerk.json +0 -545
  163. package/locales/ja-JP/clerk.json +0 -545
  164. package/locales/ko-KR/clerk.json +0 -545
  165. package/locales/nl-NL/clerk.json +0 -545
  166. package/locales/pl-PL/clerk.json +0 -545
  167. package/locales/pt-BR/clerk.json +0 -545
  168. package/locales/ru-RU/clerk.json +0 -545
  169. package/locales/tr-TR/clerk.json +0 -545
  170. package/locales/vi-VN/clerk.json +0 -545
  171. package/locales/zh-CN/clerk.json +0 -545
  172. package/locales/zh-TW/clerk.json +0 -545
  173. package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
  174. package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
  175. package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
  176. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
  177. package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
  178. package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
  179. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
  180. package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
  181. package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
  182. package/src/libs/clerk-auth/index.test.ts +0 -216
  183. package/src/libs/clerk-auth/index.ts +0 -80
  184. package/src/locales/default/clerk.ts +0 -677
  185. package/src/server/services/user/index.test.ts +0 -220
@@ -446,18 +446,20 @@ describe('StreamingExecutor actions', () => {
446
446
  });
447
447
 
448
448
  describe('effectiveAgentId for group orchestration', () => {
449
- it('should pass pre-resolved config for sub-agent when subAgentId is set in operation context', async () => {
449
+ it('should use subAgentId as agentId when groupId is present (group orchestration)', async () => {
450
450
  const { result } = renderHook(() => useChatStore());
451
451
  const messages = [createMockMessage({ role: 'user' })];
452
452
  const supervisorAgentId = 'supervisor-agent-id';
453
453
  const subAgentId = 'sub-agent-id';
454
+ const groupId = 'test-group-id';
454
455
 
455
- // Create operation with subAgentId in context (simulating group orchestration)
456
+ // Create operation with groupId and subAgentId (group orchestration scenario)
456
457
  const { operationId } = result.current.startOperation({
457
458
  type: 'execAgentRuntime',
458
459
  context: {
459
460
  agentId: supervisorAgentId,
460
461
  subAgentId: subAgentId,
462
+ groupId: groupId, // groupId present = group orchestration
461
463
  topicId: TEST_IDS.TOPIC_ID,
462
464
  messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
463
465
  },
@@ -484,14 +486,12 @@ describe('StreamingExecutor actions', () => {
484
486
  });
485
487
  });
486
488
 
487
- // With the new architecture:
488
- // - agentId param is for context/tracing (supervisor ID)
489
- // - resolvedAgentConfig contains the sub-agent's config (passed in by caller)
489
+ // In group orchestration (groupId present), subAgentId should be used as agentId
490
490
  expect(streamSpy).toHaveBeenCalledWith(
491
491
  expect.objectContaining({
492
492
  params: expect.objectContaining({
493
- agentId: supervisorAgentId, // For context/tracing purposes
494
- resolvedAgentConfig: subAgentConfig, // Pre-resolved sub-agent config
493
+ agentId: subAgentId, // subAgentId used for context injection in group orchestration
494
+ resolvedAgentConfig: subAgentConfig,
495
495
  }),
496
496
  }),
497
497
  );
@@ -499,6 +499,52 @@ describe('StreamingExecutor actions', () => {
499
499
  streamSpy.mockRestore();
500
500
  });
501
501
 
502
+ it('should use agentId when subAgentId is present but groupId is not (non-group scenario)', async () => {
503
+ const { result } = renderHook(() => useChatStore());
504
+ const messages = [createMockMessage({ role: 'user' })];
505
+ const agentId = 'normal-agent-id';
506
+ const subAgentId = 'sub-agent-id';
507
+
508
+ // Create operation with subAgentId but NO groupId (not a group orchestration scenario)
509
+ const { operationId } = result.current.startOperation({
510
+ type: 'execAgentRuntime',
511
+ context: {
512
+ agentId: agentId,
513
+ subAgentId: subAgentId, // subAgentId present but no groupId
514
+ topicId: TEST_IDS.TOPIC_ID,
515
+ messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
516
+ },
517
+ label: 'Test Non-Group with SubAgentId',
518
+ });
519
+
520
+ const streamSpy = vi
521
+ .spyOn(chatService, 'createAssistantMessageStream')
522
+ .mockImplementation(async ({ onFinish }) => {
523
+ await onFinish?.(TEST_CONTENT.AI_RESPONSE, {});
524
+ });
525
+
526
+ await act(async () => {
527
+ await result.current.internal_fetchAIChatMessage({
528
+ messages,
529
+ messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
530
+ model: 'gpt-4o-mini',
531
+ provider: 'openai',
532
+ operationId,
533
+ agentConfig: createMockResolvedAgentConfig(),
534
+ });
535
+ });
536
+
537
+ // Without groupId, should use agentId even if subAgentId is present
538
+ expect(streamSpy).toHaveBeenCalledWith(
539
+ expect.objectContaining({
540
+ // agentId used since no groupId
541
+ params: expect.objectContaining({ agentId }),
542
+ }),
543
+ );
544
+
545
+ streamSpy.mockRestore();
546
+ });
547
+
502
548
  it('should pass agentId to chatService when no subAgentId is set (normal chat)', async () => {
503
549
  const { result } = renderHook(() => useChatStore());
504
550
  const messages = [createMockMessage({ role: 'user' })];
@@ -545,7 +591,7 @@ describe('StreamingExecutor actions', () => {
545
591
  streamSpy.mockRestore();
546
592
  });
547
593
 
548
- it('should pass resolvedAgentConfig through chatService when subAgentId is present', async () => {
594
+ it('should pass resolvedAgentConfig through chatService in group orchestration speak scenario', async () => {
549
595
  const { result } = renderHook(() => useChatStore());
550
596
  const messages = [createMockMessage({ role: 'user' })];
551
597
  const supervisorAgentId = 'supervisor-agent-id';
@@ -588,15 +634,13 @@ describe('StreamingExecutor actions', () => {
588
634
  });
589
635
  });
590
636
 
591
- // With the new architecture, config is pre-resolved and passed via resolvedAgentConfig.
592
- // The agentId param is for context/tracing only.
593
- // The speaking agent's config is ensured by the caller (internal_createAgentState)
594
- // resolving config with subAgentId and passing it as agentConfig param.
637
+ // In group orchestration (groupId present), subAgentId is used as agentId for context injection
638
+ // The speaking agent's config is passed via resolvedAgentConfig
595
639
  expect(streamSpy).toHaveBeenCalledWith(
596
640
  expect.objectContaining({
597
641
  params: expect.objectContaining({
598
- // agentId is supervisor for context purposes
599
- agentId: supervisorAgentId,
642
+ // subAgentId used as agentId in group orchestration
643
+ agentId: subAgentId,
600
644
  // resolvedAgentConfig contains the speaking agent's config
601
645
  resolvedAgentConfig: speakingAgentConfig,
602
646
  }),
@@ -344,13 +344,21 @@ export const streamingExecutor: StateCreator<
344
344
  log('[internal_fetchAIChatMessage] ERROR: Operation not found: %s', operationId);
345
345
  throw new Error(`Operation not found: ${operationId}`);
346
346
  }
347
- agentId = operation.context.agentId!;
348
- subAgentId = operation.context.subAgentId;
349
347
  topicId = operation.context.topicId;
350
348
  threadId = operation.context.threadId ?? undefined;
351
349
  groupId = operation.context.groupId;
352
350
  scope = operation.context.scope;
351
+ subAgentId = operation.context.subAgentId;
353
352
  abortController = operation.abortController; // 👈 Use operation's abortController
353
+
354
+ // In group orchestration scenarios (has groupId), subAgentId is the actual responding agent
355
+ // Use it for context injection instead of the session agentId
356
+ if (groupId && subAgentId) {
357
+ agentId = subAgentId;
358
+ } else {
359
+ agentId = operation.context.agentId!;
360
+ }
361
+
354
362
  log(
355
363
  '[internal_fetchAIChatMessage] get context from operation %s: agentId=%s, subAgentId=%s, topicId=%s, groupId=%s, aborted=%s',
356
364
  operationId,
@@ -16,26 +16,18 @@ vi.mock('@/libs/swr', async () => {
16
16
  });
17
17
 
18
18
  // Use vi.hoisted to ensure variables exist before vi.mock factory executes
19
- const { enableClerk, enableNextAuth, enableBetterAuth, enableAuth } = vi.hoisted(() => ({
20
- enableClerk: { value: false },
19
+ const { enableNextAuth, enableBetterAuth } = vi.hoisted(() => ({
21
20
  enableNextAuth: { value: false },
22
21
  enableBetterAuth: { value: false },
23
- enableAuth: { value: true },
24
22
  }));
25
23
 
26
24
  vi.mock('@/envs/auth', () => ({
27
- get enableClerk() {
28
- return enableClerk.value;
29
- },
30
25
  get enableNextAuth() {
31
26
  return enableNextAuth.value;
32
27
  },
33
28
  get enableBetterAuth() {
34
29
  return enableBetterAuth.value;
35
30
  },
36
- get enableAuth() {
37
- return enableAuth.value;
38
- },
39
31
  }));
40
32
 
41
33
  const mockUserService = vi.hoisted(() => ({
@@ -59,9 +51,7 @@ afterEach(() => {
59
51
  vi.clearAllMocks();
60
52
 
61
53
  enableNextAuth.value = false;
62
- enableClerk.value = false;
63
54
  enableBetterAuth.value = false;
64
- enableAuth.value = true;
65
55
 
66
56
  // Reset store state
67
57
  useUserStore.setState({
@@ -95,34 +85,6 @@ describe('createAuthSlice', () => {
95
85
  });
96
86
 
97
87
  describe('logout', () => {
98
- it('should call clerkSignOut when Clerk is enabled', async () => {
99
- enableClerk.value = true;
100
-
101
- const clerkSignOutMock = vi.fn();
102
- useUserStore.setState({ clerkSignOut: clerkSignOutMock });
103
-
104
- const { result } = renderHook(() => useUserStore());
105
-
106
- await act(async () => {
107
- await result.current.logout();
108
- });
109
-
110
- expect(clerkSignOutMock).toHaveBeenCalled();
111
- });
112
-
113
- it('should not call clerkSignOut when Clerk is disabled', async () => {
114
- const clerkSignOutMock = vi.fn();
115
- useUserStore.setState({ clerkSignOut: clerkSignOutMock });
116
-
117
- const { result } = renderHook(() => useUserStore());
118
-
119
- await act(async () => {
120
- await result.current.logout();
121
- });
122
-
123
- expect(clerkSignOutMock).not.toHaveBeenCalled();
124
- });
125
-
126
88
  it('should call next-auth signOut when NextAuth is enabled', async () => {
127
89
  enableNextAuth.value = true;
128
90
 
@@ -152,32 +114,6 @@ describe('createAuthSlice', () => {
152
114
  });
153
115
 
154
116
  describe('openLogin', () => {
155
- it('should call clerkSignIn when Clerk is enabled', async () => {
156
- enableClerk.value = true;
157
- const clerkSignInMock = vi.fn();
158
- useUserStore.setState({ clerkSignIn: clerkSignInMock });
159
-
160
- const { result } = renderHook(() => useUserStore());
161
-
162
- await act(async () => {
163
- await result.current.openLogin();
164
- });
165
-
166
- expect(clerkSignInMock).toHaveBeenCalled();
167
- });
168
- it('should not call clerkSignIn when Clerk is disabled', async () => {
169
- const clerkSignInMock = vi.fn();
170
- useUserStore.setState({ clerkSignIn: clerkSignInMock });
171
-
172
- const { result } = renderHook(() => useUserStore());
173
-
174
- await act(async () => {
175
- await result.current.openLogin();
176
- });
177
-
178
- expect(clerkSignInMock).not.toHaveBeenCalled();
179
- });
180
-
181
117
  it('should call next-auth signIn when NextAuth is enabled', async () => {
182
118
  enableNextAuth.value = true;
183
119
 
@@ -269,22 +205,6 @@ describe('createAuthSlice', () => {
269
205
  });
270
206
  });
271
207
 
272
- describe('enableAuth', () => {
273
- it('should return true when auth is enabled', () => {
274
- enableAuth.value = true;
275
- const { result } = renderHook(() => useUserStore());
276
-
277
- expect(result.current.enableAuth()).toBe(true);
278
- });
279
-
280
- it('should return false when auth is disabled', () => {
281
- enableAuth.value = false;
282
- const { result } = renderHook(() => useUserStore());
283
-
284
- expect(result.current.enableAuth()).toBe(false);
285
- });
286
- });
287
-
288
208
  describe('fetchAuthProviders', () => {
289
209
  it('should skip fetching if already loaded', async () => {
290
210
  useUserStore.setState({ isLoadedAuthProviders: true });
@@ -1,7 +1,7 @@
1
1
  import { type SSOProvider } from '@lobechat/types';
2
2
  import { type StateCreator } from 'zustand/vanilla';
3
3
 
4
- import { enableAuth, enableBetterAuth, enableClerk, enableNextAuth } from '@/envs/auth';
4
+ import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
5
5
  import { userService } from '@/services/user';
6
6
 
7
7
  import type { UserStore } from '../../store';
@@ -12,7 +12,6 @@ interface AuthProvidersData {
12
12
  }
13
13
 
14
14
  export interface UserAuthAction {
15
- enableAuth: () => boolean;
16
15
  /**
17
16
  * Fetch auth providers (SSO accounts) for the current user
18
17
  */
@@ -66,9 +65,6 @@ export const createAuthSlice: StateCreator<
66
65
  [],
67
66
  UserAuthAction
68
67
  > = (set, get) => ({
69
- enableAuth: () => {
70
- return enableAuth;
71
- },
72
68
  fetchAuthProviders: async () => {
73
69
  // Skip if already loaded
74
70
  if (get().isLoadedAuthProviders) return;
@@ -82,12 +78,6 @@ export const createAuthSlice: StateCreator<
82
78
  }
83
79
  },
84
80
  logout: async () => {
85
- if (enableClerk) {
86
- get().clerkSignOut?.({ redirectUrl: location.toString() });
87
-
88
- return;
89
- }
90
-
91
81
  if (enableBetterAuth) {
92
82
  const { signOut } = await import('@/libs/better-auth/auth-client');
93
83
  await signOut({
@@ -109,24 +99,9 @@ export const createAuthSlice: StateCreator<
109
99
  }
110
100
  },
111
101
  openLogin: async () => {
112
- // Skip if already on a login page
102
+ // Skip if already on a Better Auth login page (/signin, /signup)
113
103
  const pathname = location.pathname;
114
- if (
115
- pathname.startsWith('/signin') ||
116
- pathname.startsWith('/signup') ||
117
- pathname.startsWith('/login')
118
- ) {
119
- return;
120
- }
121
-
122
- if (enableClerk) {
123
- const redirectUrl = location.toString();
124
- get().clerkSignIn?.({
125
- fallbackRedirectUrl: redirectUrl,
126
- signUpForceRedirectUrl: redirectUrl,
127
- signUpUrl: '/signup',
128
- });
129
-
104
+ if (pathname.startsWith('/signin') || pathname.startsWith('/signup')) {
130
105
  return;
131
106
  }
132
107
 
@@ -1,24 +1,10 @@
1
1
  import { type Session, type User } from '@auth/core/types';
2
- import {
3
- type SignInProps,
4
- type SignOut,
5
- type SignedInSessionResource,
6
- type UserProfileProps,
7
- type UserResource,
8
- } from '@clerk/types';
9
2
  import { type SSOProvider } from '@lobechat/types';
10
3
 
11
- import { enableClerk } from '@/envs/auth';
12
4
  import { type LobeUser } from '@/types/user';
13
5
 
14
6
  export interface UserAuthState {
15
7
  authProviders?: SSOProvider[];
16
- clerkOpenUserProfile?: (props?: UserProfileProps) => void;
17
- clerkSession?: SignedInSessionResource;
18
-
19
- clerkSignIn?: (props?: SignInProps) => void;
20
- clerkSignOut?: SignOut;
21
- clerkUser?: UserResource;
22
8
  /**
23
9
  * Whether user registered with email/password (credential login)
24
10
  */
@@ -33,7 +19,4 @@ export interface UserAuthState {
33
19
  user?: LobeUser;
34
20
  }
35
21
 
36
- export const initialAuthState: UserAuthState = {
37
- // Clerk doesn't need to fetch auth providers
38
- isLoadedAuthProviders: enableClerk ? true : undefined,
39
- };
22
+ export const initialAuthState: UserAuthState = {};
@@ -1,4 +1,3 @@
1
- import { BRANDING_NAME } from '@lobechat/business-const';
2
1
  import { t } from 'i18next';
3
2
  import { afterEach, describe, expect, it, vi } from 'vitest';
4
3
 
@@ -10,62 +9,16 @@ vi.mock('i18next', () => ({
10
9
  t: vi.fn((key) => key),
11
10
  }));
12
11
 
13
- // 定义一个变量来存储 enableAuth 的值
14
- let enableAuth = true;
15
- let isDesktop = false;
16
-
17
- // 模拟 @/const/auth 模块
18
- vi.mock('@/envs/auth', () => ({
19
- get enableAuth() {
20
- return enableAuth;
21
- },
22
- }));
23
-
24
- // 模拟 @/const/version 模块
25
- vi.mock('@/const/version', () => ({
26
- get isDesktop() {
27
- return isDesktop;
28
- },
29
- }));
30
-
31
12
  afterEach(() => {
32
- enableAuth = true;
33
- isDesktop = false;
13
+ vi.clearAllMocks();
34
14
  });
35
15
 
36
16
  describe('userProfileSelectors', () => {
37
17
  describe('displayUserName', () => {
38
- it('should return default username when auth is disabled and not desktop', () => {
39
- enableAuth = false;
40
- isDesktop = false;
41
-
42
- const store: UserStore = {
43
- isSignedIn: false,
44
- user: null,
45
- enableAuth: () => false,
46
- } as unknown as UserStore;
47
-
48
- expect(userProfileSelectors.displayUserName(store)).toBe(BRANDING_NAME);
49
- });
50
-
51
- it('should return user username when auth is disabled and is desktop', () => {
52
- enableAuth = false;
53
- isDesktop = true;
54
-
55
- const store: UserStore = {
56
- isSignedIn: false,
57
- user: { username: 'johndoe' },
58
- enableAuth: () => false,
59
- } as unknown as UserStore;
60
-
61
- expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
62
- });
63
-
64
18
  it('should return user username when signed in', () => {
65
19
  const store: UserStore = {
66
20
  isSignedIn: true,
67
21
  user: { username: 'johndoe' },
68
- enableAuth: () => true,
69
22
  } as UserStore;
70
23
 
71
24
  expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
@@ -75,7 +28,6 @@ describe('userProfileSelectors', () => {
75
28
  const store: UserStore = {
76
29
  isSignedIn: true,
77
30
  user: { email: 'demo@lobehub.com' },
78
- enableAuth: () => true,
79
31
  } as UserStore;
80
32
 
81
33
  expect(userProfileSelectors.displayUserName(store)).toBe('demo@lobehub.com');
@@ -83,7 +35,6 @@ describe('userProfileSelectors', () => {
83
35
 
84
36
  it('should return "anonymous" when not signed in', () => {
85
37
  const store: UserStore = {
86
- enableAuth: () => true,
87
38
  isSignedIn: false,
88
39
  user: null,
89
40
  } as unknown as UserStore;
@@ -129,40 +80,10 @@ describe('userProfileSelectors', () => {
129
80
  });
130
81
 
131
82
  describe('nickName', () => {
132
- it('should return default nickname when auth is disabled and not desktop', () => {
133
- enableAuth = false;
134
- isDesktop = false;
135
-
136
- const store: UserStore = {
137
- isSignedIn: false,
138
- user: null,
139
- enableAuth: () => false,
140
- } as unknown as UserStore;
141
-
142
- expect(userProfileSelectors.nickName(store)).toBe('userPanel.defaultNickname');
143
- expect(t).toHaveBeenCalledWith('userPanel.defaultNickname', { ns: 'common' });
144
- });
145
-
146
- it('should return user fullName when auth is disabled and is desktop', () => {
147
- enableAuth = false;
148
- isDesktop = true;
149
-
150
- const store: UserStore = {
151
- isSignedIn: false,
152
- user: { fullName: 'John Doe' },
153
- enableAuth: () => false,
154
- } as unknown as UserStore;
155
-
156
- expect(userProfileSelectors.nickName(store)).toBe('John Doe');
157
- });
158
-
159
83
  it('should return user fullName when signed in', () => {
160
- enableAuth = true;
161
-
162
84
  const store: UserStore = {
163
85
  isSignedIn: true,
164
86
  user: { fullName: 'John Doe' },
165
- enableAuth: () => true,
166
87
  } as UserStore;
167
88
 
168
89
  expect(userProfileSelectors.nickName(store)).toBe('John Doe');
@@ -172,17 +93,13 @@ describe('userProfileSelectors', () => {
172
93
  const store: UserStore = {
173
94
  isSignedIn: true,
174
95
  user: { username: 'johndoe' },
175
- enableAuth: () => true,
176
96
  } as UserStore;
177
97
 
178
98
  expect(userProfileSelectors.nickName(store)).toBe('johndoe');
179
99
  });
180
100
 
181
101
  it('should return anonymous nickname when not signed in', () => {
182
- enableAuth = true;
183
-
184
102
  const store: UserStore = {
185
- enableAuth: () => true,
186
103
  isSignedIn: false,
187
104
  user: null,
188
105
  } as unknown as UserStore;
@@ -193,37 +110,10 @@ describe('userProfileSelectors', () => {
193
110
  });
194
111
 
195
112
  describe('username', () => {
196
- it('should return default username when auth is disabled and not desktop', () => {
197
- enableAuth = false;
198
- isDesktop = false;
199
-
200
- const store: UserStore = {
201
- isSignedIn: false,
202
- user: null,
203
- enableAuth: () => false,
204
- } as unknown as UserStore;
205
-
206
- expect(userProfileSelectors.username(store)).toBe(BRANDING_NAME);
207
- });
208
-
209
- it('should return user username when auth is disabled and is desktop', () => {
210
- enableAuth = false;
211
- isDesktop = true;
212
-
213
- const store: UserStore = {
214
- isSignedIn: false,
215
- user: { username: 'johndoe' },
216
- enableAuth: () => false,
217
- } as unknown as UserStore;
218
-
219
- expect(userProfileSelectors.username(store)).toBe('johndoe');
220
- });
221
-
222
113
  it('should return user username when signed in', () => {
223
114
  const store: UserStore = {
224
115
  isSignedIn: true,
225
116
  user: { username: 'johndoe' },
226
- enableAuth: () => true,
227
117
  } as UserStore;
228
118
 
229
119
  expect(userProfileSelectors.username(store)).toBe('johndoe');
@@ -231,7 +121,6 @@ describe('userProfileSelectors', () => {
231
121
 
232
122
  it('should return "anonymous" when not signed in', () => {
233
123
  const store: UserStore = {
234
- enableAuth: () => true,
235
124
  isSignedIn: false,
236
125
  user: null,
237
126
  } as unknown as UserStore;
@@ -243,31 +132,17 @@ describe('userProfileSelectors', () => {
243
132
 
244
133
  describe('authSelectors', () => {
245
134
  describe('isLogin', () => {
246
- it('should return false when not signed in (regardless of auth enabled state)', () => {
247
- enableAuth = false;
248
-
249
- const store: UserStore = {
250
- isSignedIn: false,
251
- enableAuth: () => false,
252
- } as UserStore;
253
-
254
- // isLogin now only checks isSignedIn, not enableAuth
255
- expect(authSelectors.isLogin(store)).toBe(false);
256
- });
257
-
258
135
  it('should return true when signed in', () => {
259
136
  const store: UserStore = {
260
137
  isSignedIn: true,
261
- enableAuth: () => true,
262
138
  } as UserStore;
263
139
 
264
140
  expect(authSelectors.isLogin(store)).toBe(true);
265
141
  });
266
142
 
267
- it('should return false when not signed in and auth is enabled', () => {
143
+ it('should return false when not signed in', () => {
268
144
  const store: UserStore = {
269
145
  isSignedIn: false,
270
- enableAuth: () => true,
271
146
  } as UserStore;
272
147
 
273
148
  expect(authSelectors.isLogin(store)).toBe(false);
@@ -1,36 +1,17 @@
1
- import { BRANDING_NAME } from '@lobechat/business-const';
2
- import { isDesktop } from '@lobechat/const';
3
1
  import { type LobeUser, type SSOProvider } from '@lobechat/types';
4
2
  import { t } from 'i18next';
5
3
 
6
- import { enableAuth, enableBetterAuth, enableClerk, enableNextAuth } from '@/envs/auth';
4
+ import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
7
5
  import type { UserStore } from '@/store/user';
8
6
 
9
- const DEFAULT_USERNAME = BRANDING_NAME;
10
-
11
7
  const nickName = (s: UserStore) => {
12
8
  const defaultNickName = s.user?.fullName || s.user?.username;
13
- if (!enableAuth) {
14
- if (isDesktop) {
15
- return defaultNickName;
16
- }
17
- return t('userPanel.defaultNickname', { ns: 'common' });
18
- }
19
-
20
9
  if (s.isSignedIn) return defaultNickName;
21
10
 
22
11
  return t('userPanel.anonymousNickName', { ns: 'common' });
23
12
  };
24
13
 
25
14
  const username = (s: UserStore) => {
26
- if (!enableAuth) {
27
- if (isDesktop) {
28
- return s.user?.username;
29
- }
30
-
31
- return DEFAULT_USERNAME;
32
- }
33
-
34
15
  if (s.isSignedIn) return s.user?.username;
35
16
 
36
17
  return 'anonymous';
@@ -57,6 +38,5 @@ export const authSelectors = {
57
38
  isLogin: (s: UserStore) => s.isSignedIn,
58
39
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
59
40
  isLoginWithBetterAuth: (s: UserStore): boolean => (s.isSignedIn && enableBetterAuth) || false,
60
- isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,
61
41
  isLoginWithNextAuth: (s: UserStore): boolean => (s.isSignedIn && !!enableNextAuth) || false,
62
42
  };
@@ -6,10 +6,7 @@ import { ChatErrorType, type ErrorResponse, type ErrorType } from '@lobechat/typ
6
6
  * When these errors occur, the response will include X-Auth-Required header
7
7
  * to signal the client that re-authentication is needed.
8
8
  */
9
- const AUTH_REQUIRED_ERROR_TYPES = new Set<ErrorType>([
10
- ChatErrorType.Unauthorized,
11
- ChatErrorType.InvalidClerkUser,
12
- ]);
9
+ const AUTH_REQUIRED_ERROR_TYPES = new Set<ErrorType>([ChatErrorType.Unauthorized]);
13
10
 
14
11
  const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
15
12
  // InvalidAccessCode / InvalidAzureAPIKey / InvalidOpenAIAPIKey / InvalidZhipuAPIKey ....
@@ -0,0 +1,20 @@
1
+ import removeMarkdown from 'remove-markdown';
2
+
3
+ /**
4
+ * Convert markdown into plain text.
5
+ *
6
+ * This is a local wrapper to avoid importing third-party markdown-to-txt directly.
7
+ * It uses `remark` + `strip-markdown` under the hood.
8
+ */
9
+ export const markdownToTxt = (markdown: string): string => {
10
+ if (!markdown) return '';
11
+
12
+ try {
13
+ return removeMarkdown(markdown).trimEnd();
14
+ } catch {
15
+ // Best-effort: fall back to raw input when parsing fails.
16
+ return markdown;
17
+ }
18
+ };
19
+
20
+ export default markdownToTxt;