@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.
- package/.cursor/rules/i18n.mdc +1 -1
- package/.cursor/rules/modal-imperative.mdc +162 -0
- package/.cursor/rules/rules-index.mdc +1 -0
- package/.env.example +0 -14
- package/.eslintrc.js +8 -1
- package/CHANGELOG.md +66 -0
- package/Dockerfile +3 -13
- package/README.md +3 -5
- package/README.zh-CN.md +3 -5
- package/changelog/v1.json +24 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
- package/e2e/src/support/webServer.ts +2 -0
- package/locales/ar/error.json +0 -4
- package/locales/bg-BG/error.json +0 -4
- package/locales/de-DE/error.json +0 -4
- package/locales/en-US/error.json +0 -4
- package/locales/es-ES/error.json +0 -4
- package/locales/fa-IR/error.json +0 -4
- package/locales/fr-FR/error.json +0 -4
- package/locales/it-IT/error.json +0 -4
- package/locales/ja-JP/error.json +0 -4
- package/locales/ko-KR/error.json +0 -4
- package/locales/nl-NL/error.json +0 -4
- package/locales/pl-PL/error.json +0 -4
- package/locales/pt-BR/error.json +0 -4
- package/locales/ru-RU/error.json +0 -4
- package/locales/tr-TR/error.json +0 -4
- package/locales/vi-VN/error.json +0 -4
- package/locales/zh-CN/error.json +0 -4
- package/locales/zh-TW/error.json +0 -4
- package/package.json +7 -9
- package/packages/builtin-agents/package.json +2 -0
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +161 -12
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
- package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
- package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
- package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
- package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
- package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
- package/packages/model-bank/src/modelProviders/fal.ts +0 -1
- package/packages/types/src/fetch.ts +1 -2
- package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
- package/packages/utils/src/server/auth.ts +1 -9
- package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
- package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
- package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
- package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
- package/scripts/countEnWord.ts +1 -1
- package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
- package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
- package/scripts/prebuild.mts +10 -8
- package/scripts/serverLauncher/startServer.js +23 -5
- package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
- package/src/app/(backend)/middleware/auth/index.ts +0 -15
- package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
- package/src/app/(backend)/middleware/auth/utils.ts +2 -17
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
- package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
- package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
- package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
- package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
- package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
- package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
- package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
- package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
- package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
- package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
- package/src/app/robots.tsx +1 -1
- package/src/envs/auth.ts +2 -27
- package/src/envs/llm.ts +2 -2
- package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
- package/src/features/ChatMiniMap/utils.ts +1 -1
- package/src/features/CommandMenu/SearchResults.tsx +1 -1
- package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
- package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
- package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
- package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
- package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
- package/src/features/IntegrationDetailModal/index.tsx +21 -283
- package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
- package/src/features/ProfileEditor/AgentTool.tsx +14 -20
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
- package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
- package/src/features/SkillStore/Search/index.tsx +1 -1
- package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
- package/src/features/SkillStore/index.tsx +15 -33
- package/src/features/User/UserPanel/PanelContent.tsx +0 -8
- package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
- package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
- package/src/features/User/__tests__/useMenu.test.tsx +2 -43
- package/src/layout/AuthProvider/index.tsx +0 -5
- package/src/libs/next/config/define-config.ts +6 -0
- package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
- package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
- package/src/libs/next/proxy/define-config.ts +4 -53
- package/src/libs/next-auth/adapter/index.ts +1 -2
- package/src/libs/oidc-provider/provider.test.ts +5 -316
- package/src/libs/trpc/lambda/context.test.ts +0 -13
- package/src/libs/trpc/lambda/context.ts +3 -22
- package/src/libs/trpc/middleware/userAuth.ts +2 -4
- package/src/libs/trusted-client/getSessionUser.ts +2 -17
- package/src/locales/default/error.ts +0 -6
- package/src/locales/default/index.ts +0 -2
- package/src/proxy.ts +0 -1
- package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
- package/src/server/routers/lambda/user.ts +6 -63
- package/src/server/services/changelog/index.test.ts +3 -2
- package/src/server/services/changelog/index.ts +1 -1
- package/src/server/services/user/index.ts +0 -83
- package/src/services/chat/index.ts +1 -2
- package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
- package/src/store/user/slices/auth/action.test.ts +1 -81
- package/src/store/user/slices/auth/action.ts +3 -28
- package/src/store/user/slices/auth/initialState.ts +1 -18
- package/src/store/user/slices/auth/selectors.test.ts +2 -127
- package/src/store/user/slices/auth/selectors.ts +1 -21
- package/src/utils/errorResponse.ts +1 -4
- package/src/utils/markdownToTxt.ts +20 -0
- package/locales/ar/clerk.json +0 -545
- package/locales/bg-BG/clerk.json +0 -545
- package/locales/de-DE/clerk.json +0 -545
- package/locales/en-US/clerk.json +0 -545
- package/locales/es-ES/clerk.json +0 -545
- package/locales/fa-IR/clerk.json +0 -545
- package/locales/fr-FR/clerk.json +0 -545
- package/locales/it-IT/clerk.json +0 -545
- package/locales/ja-JP/clerk.json +0 -545
- package/locales/ko-KR/clerk.json +0 -545
- package/locales/nl-NL/clerk.json +0 -545
- package/locales/pl-PL/clerk.json +0 -545
- package/locales/pt-BR/clerk.json +0 -545
- package/locales/ru-RU/clerk.json +0 -545
- package/locales/tr-TR/clerk.json +0 -545
- package/locales/vi-VN/clerk.json +0 -545
- package/locales/zh-CN/clerk.json +0 -545
- package/locales/zh-TW/clerk.json +0 -545
- package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
- package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
- package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
- package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
- package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
- package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
- package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
- package/src/libs/clerk-auth/index.test.ts +0 -216
- package/src/libs/clerk-auth/index.ts +0 -80
- package/src/locales/default/clerk.ts +0 -677
- 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,
|
|
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 (
|
|
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,
|
|
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 (
|
|
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
|
@@ -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
|
|
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
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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('
|
|
23
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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:
|
|
494
|
-
resolvedAgentConfig: subAgentConfig,
|
|
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
|
|
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
|
-
//
|
|
592
|
-
// The
|
|
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
|
-
//
|
|
599
|
-
agentId:
|
|
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,
|