@lobehub/lobehub 2.0.0-next.343 → 2.0.0-next.345

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 (169) 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/Dockerfile +3 -13
  8. package/README.md +3 -5
  9. package/README.zh-CN.md +3 -5
  10. package/changelog/v1.json +24 -0
  11. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
  12. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
  13. package/e2e/src/support/webServer.ts +2 -0
  14. package/locales/ar/error.json +0 -4
  15. package/locales/bg-BG/error.json +0 -4
  16. package/locales/de-DE/error.json +0 -4
  17. package/locales/en-US/error.json +0 -4
  18. package/locales/es-ES/error.json +0 -4
  19. package/locales/fa-IR/error.json +0 -4
  20. package/locales/fr-FR/error.json +0 -4
  21. package/locales/it-IT/error.json +0 -4
  22. package/locales/ja-JP/error.json +0 -4
  23. package/locales/ko-KR/error.json +0 -4
  24. package/locales/nl-NL/error.json +0 -4
  25. package/locales/pl-PL/error.json +0 -4
  26. package/locales/pt-BR/error.json +0 -4
  27. package/locales/ru-RU/error.json +0 -4
  28. package/locales/tr-TR/error.json +0 -4
  29. package/locales/vi-VN/error.json +0 -4
  30. package/locales/zh-CN/error.json +0 -4
  31. package/locales/zh-TW/error.json +0 -4
  32. package/package.json +7 -9
  33. package/packages/builtin-agents/package.json +2 -0
  34. package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
  35. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
  36. package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
  37. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +161 -12
  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/model-bank/src/modelProviders/comfyui.ts +0 -1
  44. package/packages/model-bank/src/modelProviders/fal.ts +0 -1
  45. package/packages/types/src/fetch.ts +1 -2
  46. package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
  47. package/packages/utils/src/server/auth.ts +1 -9
  48. package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
  49. package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
  50. package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
  51. package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
  52. package/scripts/countEnWord.ts +1 -1
  53. package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
  54. package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
  55. package/scripts/prebuild.mts +10 -8
  56. package/scripts/serverLauncher/startServer.js +23 -5
  57. package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
  58. package/src/app/(backend)/middleware/auth/index.ts +0 -15
  59. package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
  60. package/src/app/(backend)/middleware/auth/utils.ts +2 -17
  61. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
  62. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
  63. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
  64. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
  65. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
  66. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
  67. package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
  68. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
  69. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
  70. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
  71. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
  72. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
  73. package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
  74. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
  75. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
  76. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
  77. package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
  78. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
  79. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  80. package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
  81. package/src/app/robots.tsx +1 -1
  82. package/src/envs/auth.ts +2 -27
  83. package/src/envs/llm.ts +2 -2
  84. package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
  85. package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
  86. package/src/features/ChatMiniMap/utils.ts +1 -1
  87. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  88. package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
  89. package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
  90. package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
  91. package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
  92. package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
  93. package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
  94. package/src/features/IntegrationDetailModal/index.tsx +21 -283
  95. package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
  96. package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
  97. package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
  98. package/src/features/ProfileEditor/AgentTool.tsx +14 -20
  99. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
  100. package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
  101. package/src/features/SkillStore/Search/index.tsx +1 -1
  102. package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
  103. package/src/features/SkillStore/index.tsx +15 -33
  104. package/src/features/User/UserPanel/PanelContent.tsx +0 -8
  105. package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
  106. package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
  107. package/src/features/User/__tests__/useMenu.test.tsx +2 -43
  108. package/src/layout/AuthProvider/index.tsx +0 -5
  109. package/src/libs/next/config/define-config.ts +6 -0
  110. package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
  111. package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
  112. package/src/libs/next/proxy/define-config.ts +4 -53
  113. package/src/libs/next-auth/adapter/index.ts +1 -2
  114. package/src/libs/oidc-provider/provider.test.ts +5 -316
  115. package/src/libs/trpc/lambda/context.test.ts +0 -13
  116. package/src/libs/trpc/lambda/context.ts +3 -22
  117. package/src/libs/trpc/middleware/userAuth.ts +2 -4
  118. package/src/libs/trusted-client/getSessionUser.ts +2 -17
  119. package/src/locales/default/error.ts +0 -6
  120. package/src/locales/default/index.ts +0 -2
  121. package/src/proxy.ts +0 -1
  122. package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
  123. package/src/server/routers/lambda/user.ts +6 -63
  124. package/src/server/services/changelog/index.test.ts +3 -2
  125. package/src/server/services/changelog/index.ts +1 -1
  126. package/src/server/services/user/index.ts +0 -83
  127. package/src/services/chat/index.ts +1 -2
  128. package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
  129. package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
  130. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
  131. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
  132. package/src/store/user/slices/auth/action.test.ts +1 -81
  133. package/src/store/user/slices/auth/action.ts +3 -28
  134. package/src/store/user/slices/auth/initialState.ts +1 -18
  135. package/src/store/user/slices/auth/selectors.test.ts +2 -127
  136. package/src/store/user/slices/auth/selectors.ts +1 -21
  137. package/src/utils/errorResponse.ts +1 -4
  138. package/src/utils/markdownToTxt.ts +20 -0
  139. package/locales/ar/clerk.json +0 -545
  140. package/locales/bg-BG/clerk.json +0 -545
  141. package/locales/de-DE/clerk.json +0 -545
  142. package/locales/en-US/clerk.json +0 -545
  143. package/locales/es-ES/clerk.json +0 -545
  144. package/locales/fa-IR/clerk.json +0 -545
  145. package/locales/fr-FR/clerk.json +0 -545
  146. package/locales/it-IT/clerk.json +0 -545
  147. package/locales/ja-JP/clerk.json +0 -545
  148. package/locales/ko-KR/clerk.json +0 -545
  149. package/locales/nl-NL/clerk.json +0 -545
  150. package/locales/pl-PL/clerk.json +0 -545
  151. package/locales/pt-BR/clerk.json +0 -545
  152. package/locales/ru-RU/clerk.json +0 -545
  153. package/locales/tr-TR/clerk.json +0 -545
  154. package/locales/vi-VN/clerk.json +0 -545
  155. package/locales/zh-CN/clerk.json +0 -545
  156. package/locales/zh-TW/clerk.json +0 -545
  157. package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
  158. package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
  159. package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
  160. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
  161. package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
  162. package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
  163. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
  164. package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
  165. package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
  166. package/src/libs/clerk-auth/index.test.ts +0 -216
  167. package/src/libs/clerk-auth/index.ts +0 -80
  168. package/src/locales/default/clerk.ts +0 -677
  169. package/src/server/services/user/index.test.ts +0 -220
@@ -1,6 +1,6 @@
1
1
  import { TRPCError } from '@trpc/server';
2
2
 
3
- import { enableBetterAuth, enableClerk, enableNextAuth } from '@/envs/auth';
3
+ import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
4
4
 
5
5
  import { trpc } from '../lambda/init';
6
6
 
@@ -9,9 +9,7 @@ export const userAuth = trpc.middleware(async (opts) => {
9
9
 
10
10
  // `ctx.user` is nullable
11
11
  if (!ctx.userId) {
12
- if (enableClerk) {
13
- console.log('clerk auth:', ctx.clerkAuth);
14
- } else if (enableBetterAuth) {
12
+ if (enableBetterAuth) {
15
13
  console.log('better auth: no session found in context');
16
14
  } else if (enableNextAuth) {
17
15
  console.log('next auth:', ctx.nextAuth);
@@ -1,30 +1,15 @@
1
- import { enableBetterAuth, enableClerk, enableNextAuth } from '@/envs/auth';
1
+ import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
2
2
 
3
3
  import type { TrustedClientUserInfo } from './index';
4
4
 
5
5
  /**
6
6
  * Get user info from the current session for trusted client authentication
7
- * This works with different authentication providers (Clerk, BetterAuth, NextAuth)
7
+ * This works with different authentication providers (BetterAuth, NextAuth)
8
8
  *
9
9
  * @returns User info or undefined if not authenticated
10
10
  */
11
11
  export const getSessionUser = async (): Promise<TrustedClientUserInfo | undefined> => {
12
12
  try {
13
- if (enableClerk) {
14
- const { currentUser } = await import('@clerk/nextjs/server');
15
- const user = await currentUser();
16
-
17
- if (!user?.id || !user?.primaryEmailAddress?.emailAddress) {
18
- return undefined;
19
- }
20
-
21
- return {
22
- email: user.primaryEmailAddress.emailAddress,
23
- name: user.fullName || user.firstName || undefined,
24
- userId: user.id,
25
- };
26
- }
27
-
28
13
  if (enableBetterAuth) {
29
14
  const { headers } = await import('next/headers');
30
15
  const { auth } = await import('@/auth');
@@ -1,10 +1,6 @@
1
1
  import { businessErrorsLocales } from '@/business/locales/errors';
2
2
 
3
3
  export default {
4
- 'clerkAuth.loginSuccess.action': 'Continue Session',
5
- 'clerkAuth.loginSuccess.desc':
6
- "{{greeting}}, it's great to continue serving you. Let's pick up where we left off.",
7
- 'clerkAuth.loginSuccess.title': 'Welcome back, {{nickName}}',
8
4
  'error.backHome': 'Back to Home',
9
5
  'error.desc': 'Give it a try later, or go back to the known world.',
10
6
  'error.retry': 'Reload',
@@ -143,8 +139,6 @@ export default {
143
139
  'Invalid access code or empty. Please enter the correct access code or add a custom API Key.',
144
140
  'response.InvalidBedrockCredentials':
145
141
  'Bedrock authentication failed. Please check the AccessKeyId/SecretAccessKey and retry.',
146
- 'response.InvalidClerkUser':
147
- 'Sorry, you are not currently logged in. Please log in or register an account to continue.',
148
142
  'response.InvalidComfyUIArgs':
149
143
  'Invalid ComfyUI configuration. Please check the settings and try again.',
150
144
  'response.InvalidGithubToken':
@@ -5,7 +5,6 @@ import auth from './auth';
5
5
  import authError from './authError';
6
6
  import changelog from './changelog';
7
7
  import chat from './chat';
8
- import clerk from './clerk';
9
8
  import color from './color';
10
9
  import common from './common';
11
10
  import components from './components';
@@ -47,7 +46,6 @@ const resources = {
47
46
  authError,
48
47
  changelog,
49
48
  chat,
50
- clerk,
51
49
  color,
52
50
  common,
53
51
  components,
package/src/proxy.ts CHANGED
@@ -31,7 +31,6 @@ export const config = {
31
31
  '/desktop-onboarding(.*)',
32
32
  '/onboarding',
33
33
 
34
- '/login(.*)',
35
34
  '/signup(.*)',
36
35
  '/signin(.*)',
37
36
  '/verify-email(.*)',
@@ -5,7 +5,6 @@ import { MessageModel } from '@/database/models/message';
5
5
  import { SessionModel } from '@/database/models/session';
6
6
  import { UserModel, UserNotFoundError } from '@/database/models/user';
7
7
  import { serverDB } from '@/database/server';
8
- import { enableClerk } from '@/envs/auth';
9
8
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
10
9
  import { NextAuthUserService } from '@/server/services/nextAuthUser';
11
10
  import { UserService } from '@/server/services/user';
@@ -13,10 +12,6 @@ import { UserService } from '@/server/services/user';
13
12
  import { userRouter } from '../user';
14
13
 
15
14
  // Mock modules
16
- vi.mock('@clerk/nextjs/server', () => ({
17
- currentUser: vi.fn(),
18
- }));
19
-
20
15
  vi.mock('@/database/server', () => ({
21
16
  serverDB: {},
22
17
  }));
@@ -30,7 +25,6 @@ vi.mock('@/server/services/user');
30
25
  vi.mock('@/server/services/nextAuthUser');
31
26
  vi.mock('@/envs/auth', () => ({
32
27
  enableBetterAuth: false,
33
- enableClerk: true,
34
28
  enableNextAuth: false,
35
29
  }));
36
30
 
@@ -129,71 +123,6 @@ describe('userRouter', () => {
129
123
  });
130
124
  });
131
125
 
132
- it('should create new user when user not found (clerk enabled)', async () => {
133
- const mockClerkUser = {
134
- id: mockUserId,
135
- createdAt: new Date(),
136
- emailAddresses: [{ id: 'email-1', emailAddress: 'test@example.com' }],
137
- firstName: 'Test',
138
- lastName: 'User',
139
- imageUrl: 'avatar.jpg',
140
- phoneNumbers: [],
141
- primaryEmailAddressId: 'email-1',
142
- primaryPhoneNumberId: null,
143
- username: 'testuser',
144
- };
145
-
146
- const { currentUser } = await import('@clerk/nextjs/server');
147
- vi.mocked(currentUser).mockResolvedValue(mockClerkUser as any);
148
-
149
- vi.mocked(UserService).mockImplementation(
150
- () =>
151
- ({
152
- createUser: vi.fn().mockResolvedValue({ success: true }),
153
- }) as any,
154
- );
155
-
156
- vi.mocked(UserModel).mockImplementation(
157
- () =>
158
- ({
159
- getUserState: vi
160
- .fn()
161
- .mockRejectedValueOnce(new UserNotFoundError())
162
- .mockResolvedValueOnce({
163
- isOnboarded: false,
164
- preference: { telemetry: null },
165
- settings: {},
166
- }),
167
- updateUser: vi.fn().mockResolvedValue({ rowCount: 1 }),
168
- }) as any,
169
- );
170
-
171
- vi.mocked(MessageModel).mockImplementation(
172
- () =>
173
- ({
174
- countUpTo: vi.fn().mockResolvedValue(0),
175
- }) as any,
176
- );
177
-
178
- vi.mocked(SessionModel).mockImplementation(
179
- () =>
180
- ({
181
- hasMoreThanN: vi.fn().mockResolvedValue(false),
182
- }) as any,
183
- );
184
-
185
- const result = await userRouter.createCaller({ ...mockCtx } as any).getUserState();
186
-
187
- expect(result).toMatchObject({
188
- isOnboard: false,
189
- preference: { telemetry: null },
190
- settings: {},
191
- hasConversation: false,
192
- canEnablePWAGuide: false,
193
- canEnableTrace: false,
194
- userId: mockUserId,
195
- });
196
- });
197
126
  });
198
127
 
199
128
  describe('makeUserOnboarded', () => {
@@ -1,4 +1,3 @@
1
- import { type UserJSON } from '@clerk/backend';
2
1
  import { isDesktop } from '@lobechat/const';
3
2
  import {
4
3
  NextAuthAccountSchame,
@@ -24,17 +23,13 @@ import {
24
23
  } from '@/business/server/user';
25
24
  import { MessageModel } from '@/database/models/message';
26
25
  import { SessionModel } from '@/database/models/session';
27
- import { UserModel, UserNotFoundError } from '@/database/models/user';
28
- import { enableClerk } from '@/envs/auth';
29
- import { ClerkAuth } from '@/libs/clerk-auth';
30
- import { pino } from '@/libs/logger';
26
+ import { UserModel } from '@/database/models/user';
31
27
  import { authedProcedure, router } from '@/libs/trpc/lambda';
32
28
  import { serverDatabase } from '@/libs/trpc/lambda/middleware';
33
29
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
34
30
  import { FileS3 } from '@/server/modules/S3';
35
31
  import { FileService } from '@/server/services/file';
36
32
  import { NextAuthUserService } from '@/server/services/nextAuthUser';
37
- import { UserService } from '@/server/services/user';
38
33
 
39
34
  const usernameSchema = z
40
35
  .string()
@@ -45,7 +40,6 @@ const usernameSchema = z
45
40
  const userProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next }) => {
46
41
  return next({
47
42
  ctx: {
48
- clerkAuth: new ClerkAuth(),
49
43
  fileService: new FileService(ctx.serverDB, ctx.userId),
50
44
  messageModel: new MessageModel(ctx.serverDB, ctx.userId),
51
45
  nextAuthUserService: new NextAuthUserService(ctx.serverDB),
@@ -77,61 +71,10 @@ export const userRouter = router({
77
71
  // `after` may fail outside request scope (e.g., in tests), ignore silently
78
72
  }
79
73
 
80
- // Helper function to get or create user state
81
- const getOrCreateUserState = async () => {
82
- let state: Awaited<ReturnType<UserModel['getUserState']>> | undefined;
83
-
84
- // get or create first-time user
85
- while (!state) {
86
- try {
87
- state = await ctx.userModel.getUserState(KeyVaultsGateKeeper.getUserKeyVaults);
88
- } catch (error) {
89
- // user not create yet
90
- if (error instanceof UserNotFoundError) {
91
- // if in clerk auth mode
92
- if (enableClerk) {
93
- const user = await ctx.clerkAuth.getCurrentUser();
94
- if (user) {
95
- const userService = new UserService(ctx.serverDB);
96
-
97
- await userService.createUser(user.id, {
98
- created_at: user.createdAt,
99
- email_addresses: user.emailAddresses.map((e) => ({
100
- email_address: e.emailAddress,
101
- id: e.id,
102
- })),
103
- first_name: user.firstName,
104
- id: user.id,
105
- image_url: user.imageUrl,
106
- last_name: user.lastName,
107
- phone_numbers: user.phoneNumbers.map((e) => ({
108
- id: e.id,
109
- phone_number: e.phoneNumber,
110
- })),
111
- primary_email_address_id: user.primaryEmailAddressId,
112
- primary_phone_number_id: user.primaryPhoneNumberId,
113
- username: user.username,
114
- } as UserJSON);
115
-
116
- continue;
117
- }
118
- }
119
-
120
- // if in desktop mode, make sure desktop user exist
121
- else if (isDesktop) {
122
- await UserModel.makeSureUserExist(ctx.serverDB, ctx.userId);
123
- pino.info('create desktop user');
124
- continue;
125
- }
126
- }
127
-
128
- console.error('getUserState:', error);
129
- throw error;
130
- }
131
- }
132
-
133
- return state;
134
- };
74
+ // For desktop mode, ensure user exists before getting state
75
+ if (isDesktop) {
76
+ await UserModel.makeSureUserExist(ctx.serverDB, ctx.userId);
77
+ }
135
78
 
136
79
  // Run user state fetch and count queries in parallel
137
80
  const [
@@ -143,7 +86,7 @@ export const userRouter = router({
143
86
  isInWaitList,
144
87
  isInviteCodeRequired,
145
88
  ] = await Promise.all([
146
- getOrCreateUserState(),
89
+ ctx.userModel.getUserState(KeyVaultsGateKeeper.getUserKeyVaults),
147
90
  ctx.messageModel.countUpTo(5),
148
91
  ctx.sessionModel.hasMoreThanN(1),
149
92
  getReferralStatus(ctx.userId),
@@ -19,8 +19,9 @@ vi.mock('gray-matter', () => ({
19
19
  })),
20
20
  }));
21
21
 
22
- vi.mock('markdown-to-txt', () => ({
23
- markdownToTxt: vi.fn().mockImplementation((text) => text),
22
+ vi.mock('@/utils/markdownToTxt', () => ({
23
+ default: vi.fn().mockImplementation((text: string) => text),
24
+ markdownToTxt: vi.fn().mockImplementation((text: string) => text),
24
25
  }));
25
26
 
26
27
  vi.mock('semver', async (importOriginal) => {
@@ -1,13 +1,13 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { template } from 'es-toolkit/compat';
3
3
  import matter from 'gray-matter';
4
- import { markdownToTxt } from 'markdown-to-txt';
5
4
  import semver from 'semver';
6
5
  import urlJoin from 'url-join';
7
6
 
8
7
  import { FetchCacheTag } from '@/const/cacheControl';
9
8
  import { type Locales } from '@/locales/resources';
10
9
  import { type ChangelogIndexItem } from '@/types/changelog';
10
+ import { markdownToTxt } from '@/utils/markdownToTxt';
11
11
 
12
12
  const URL_TEMPLATE = 'https://raw.githubusercontent.com/{{user}}/{{repo}}/{{branch}}/{{path}}';
13
13
  const LAST_MODIFIED = new Date().toISOString();
@@ -1,6 +1,4 @@
1
-
2
1
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
3
- import { type UserJSON } from '@clerk/backend';
4
2
  import { type LobeChatDatabase } from '@lobechat/database';
5
3
 
6
4
  import { initNewUserForBusiness } from '@/business/server/user';
@@ -54,87 +52,6 @@ export class UserService {
54
52
  });
55
53
  }
56
54
 
57
- createUser = async (id: string, params: UserJSON) => {
58
- // Check if user already exists
59
- const res = await UserModel.findById(this.db, id);
60
-
61
- // If user already exists, skip creating a new user
62
- if (res)
63
- return {
64
- message: 'user not created due to user already existing in the database',
65
- success: false,
66
- };
67
-
68
- const email = params.email_addresses.find((e) => e.id === params.primary_email_address_id);
69
-
70
- const phone = params.phone_numbers.find((e, index) => {
71
- if (!!params.primary_phone_number_id) return e.id === params.primary_phone_number_id;
72
-
73
- return index === 0;
74
- });
75
-
76
- // 2. create user in database
77
- await UserModel.createUser(this.db, {
78
- avatar: params.image_url,
79
- clerkCreatedAt: new Date(params.created_at),
80
- email: email?.email_address,
81
- firstName: params.first_name,
82
- id,
83
- lastName: params.last_name,
84
- phone: phone?.phone_number,
85
- username: params.username,
86
- });
87
-
88
- await this.initUser({
89
- email: email?.email_address,
90
- firstName: params.first_name,
91
- id,
92
- lastName: params.last_name,
93
- phone: phone?.phone_number,
94
- username: params.username,
95
- });
96
-
97
- return { message: 'user created', success: true };
98
- };
99
-
100
- deleteUser = async (id: string) => {
101
- await UserModel.deleteUser(this.db, id);
102
- };
103
-
104
- updateUser = async (id: string, params: UserJSON) => {
105
- const userModel = new UserModel(this.db, id);
106
-
107
- // Check if user already exists
108
- const res = await UserModel.findById(this.db, id);
109
-
110
- // If user not exists, skip update the user
111
- if (!res)
112
- return {
113
- message: "user not updated due to the user don't existing in the database",
114
- success: false,
115
- };
116
-
117
- pino.info('updating user due to clerk webhook');
118
-
119
- const email = params.email_addresses.find((e) => e.id === params.primary_email_address_id);
120
- const phone = params.phone_numbers.find((e, index) => {
121
- if (params.primary_phone_number_id) return e.id === params.primary_phone_number_id;
122
- return index === 0;
123
- });
124
-
125
- await userModel.updateUser({
126
- avatar: params.image_url,
127
- email: email?.email_address,
128
- firstName: params.first_name,
129
- id,
130
- lastName: params.last_name,
131
- phone: phone?.phone_number,
132
- username: params.username,
133
- });
134
-
135
- return { message: 'user updated', success: true };
136
- };
137
-
138
55
  getUserApiKeys = async (id: string) => {
139
56
  return UserModel.getUserApiKeys(this.db, id, KeyVaultsGateKeeper.getUserKeyVaults);
140
57
  };
@@ -24,7 +24,6 @@ import { merge } from 'es-toolkit/compat';
24
24
  import { ModelProvider } from 'model-bank';
25
25
 
26
26
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
27
- import { enableAuth } from '@/envs/auth';
28
27
  import { getSearchConfig } from '@/helpers/getSearchConfig';
29
28
  import { createAgentToolsEngine } from '@/helpers/toolEngineering';
30
29
  import { getAgentStoreState } from '@/store/agent';
@@ -542,7 +541,7 @@ class ChatService {
542
541
  * if enable login and not signed in, return unauthorized error
543
542
  */
544
543
  const userStore = useUserStore.getState();
545
- if (enableAuth && !userStore.isSignedIn) {
544
+ if (!userStore.isSignedIn) {
546
545
  throw AgentRuntimeError.createError(ChatErrorType.InvalidAccessCode);
547
546
  }
548
547
 
@@ -444,6 +444,49 @@ describe('resolveAgentConfig', () => {
444
444
  expect(result.plugins).toContain(NotebookIdentifier);
445
445
  expect(result.plugins).toContain('user-plugin');
446
446
  });
447
+
448
+ it('should use basePlugins from agentConfig when ctx.plugins is not provided', () => {
449
+ // This test verifies the fix for the issue where INBOX agent lost user-configured plugins
450
+ // when resolveAgentConfig was called without the plugins parameter.
451
+ // The runtime function should receive basePlugins (from agentConfig) as fallback.
452
+ const userConfiguredPlugins = ['web-search', 'memory', 'custom-tool'];
453
+
454
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
455
+ () =>
456
+ ({
457
+ ...mockAgentConfig,
458
+ plugins: userConfiguredPlugins,
459
+ }) as any,
460
+ );
461
+
462
+ // Simulate INBOX runtime behavior: merges builtin tools with ctx.plugins
463
+ const getAgentRuntimeConfigSpy = vi
464
+ .spyOn(builtinAgents, 'getAgentRuntimeConfig')
465
+ .mockImplementation((slug, ctx) => ({
466
+ // This simulates the actual INBOX runtime: [GTDIdentifier, NotebookIdentifier, ...(ctx.plugins || [])]
467
+ plugins: [GTDIdentifier, NotebookIdentifier, ...(ctx.plugins || [])],
468
+ systemRole: 'Inbox system role',
469
+ }));
470
+
471
+ // Call WITHOUT plugins parameter - this is how internal_createAgentState calls it
472
+ const result = resolveAgentConfig({ agentId: 'inbox-agent' });
473
+
474
+ // Verify getAgentRuntimeConfig received basePlugins as fallback
475
+ expect(getAgentRuntimeConfigSpy).toHaveBeenCalledWith(
476
+ 'inbox',
477
+ expect.objectContaining({
478
+ plugins: userConfiguredPlugins,
479
+ }),
480
+ );
481
+
482
+ // Verify final plugins include both builtin tools AND user-configured plugins
483
+ expect(result.plugins).toContain(GTDIdentifier);
484
+ expect(result.plugins).toContain(NotebookIdentifier);
485
+ expect(result.plugins).toContain('web-search');
486
+ expect(result.plugins).toContain('memory');
487
+ expect(result.plugins).toContain('custom-tool');
488
+ expect(result.plugins).toHaveLength(5); // 2 builtin + 3 user plugins
489
+ });
447
490
  });
448
491
  });
449
492
 
@@ -297,11 +297,13 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
297
297
  }
298
298
 
299
299
  // Builtin agent - merge runtime config
300
+ // Use basePlugins as fallback when ctx.plugins is not provided
301
+ // This ensures builtin agents (e.g., INBOX) receive user-configured plugins for merging
300
302
  const runtimeConfig = getAgentRuntimeConfig(slug, {
301
303
  documentContent,
302
304
  groupSupervisorContext,
303
305
  model,
304
- plugins,
306
+ plugins: plugins || basePlugins,
305
307
  targetAgentConfig,
306
308
  });
307
309
 
@@ -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,