@lobehub/lobehub 2.0.0-next.304 โ†’ 2.0.0-next.305

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 (55) hide show
  1. package/.github/workflows/manual-build-desktop.yml +11 -1
  2. package/CHANGELOG.md +25 -0
  3. package/apps/desktop/.i18nrc.js +3 -3
  4. package/apps/desktop/electron.vite.config.ts +0 -2
  5. package/apps/desktop/resources/locales/ar/dialog.json +5 -1
  6. package/apps/desktop/resources/locales/ar/menu.json +16 -0
  7. package/apps/desktop/resources/locales/bg-BG/dialog.json +5 -1
  8. package/apps/desktop/resources/locales/bg-BG/menu.json +16 -0
  9. package/apps/desktop/resources/locales/de-DE/dialog.json +5 -1
  10. package/apps/desktop/resources/locales/de-DE/menu.json +16 -0
  11. package/apps/desktop/resources/locales/en/common.json +26 -0
  12. package/apps/desktop/resources/locales/en/dialog.json +27 -0
  13. package/apps/desktop/resources/locales/en/menu.json +73 -0
  14. package/apps/desktop/resources/locales/es-ES/dialog.json +5 -1
  15. package/apps/desktop/resources/locales/es-ES/menu.json +16 -0
  16. package/apps/desktop/resources/locales/fa-IR/dialog.json +5 -1
  17. package/apps/desktop/resources/locales/fa-IR/menu.json +16 -0
  18. package/apps/desktop/resources/locales/fr-FR/dialog.json +5 -1
  19. package/apps/desktop/resources/locales/fr-FR/menu.json +16 -0
  20. package/apps/desktop/resources/locales/it-IT/dialog.json +5 -1
  21. package/apps/desktop/resources/locales/it-IT/menu.json +16 -0
  22. package/apps/desktop/resources/locales/ja-JP/dialog.json +5 -1
  23. package/apps/desktop/resources/locales/ja-JP/menu.json +16 -0
  24. package/apps/desktop/resources/locales/ko-KR/dialog.json +5 -1
  25. package/apps/desktop/resources/locales/ko-KR/menu.json +16 -0
  26. package/apps/desktop/resources/locales/nl-NL/dialog.json +5 -1
  27. package/apps/desktop/resources/locales/nl-NL/menu.json +16 -0
  28. package/apps/desktop/resources/locales/pl-PL/dialog.json +5 -1
  29. package/apps/desktop/resources/locales/pl-PL/menu.json +16 -0
  30. package/apps/desktop/resources/locales/pt-BR/dialog.json +5 -1
  31. package/apps/desktop/resources/locales/pt-BR/menu.json +16 -0
  32. package/apps/desktop/resources/locales/ru-RU/dialog.json +5 -1
  33. package/apps/desktop/resources/locales/ru-RU/menu.json +16 -0
  34. package/apps/desktop/resources/locales/tr-TR/dialog.json +5 -1
  35. package/apps/desktop/resources/locales/tr-TR/menu.json +16 -0
  36. package/apps/desktop/resources/locales/vi-VN/dialog.json +5 -1
  37. package/apps/desktop/resources/locales/vi-VN/menu.json +16 -0
  38. package/apps/desktop/resources/locales/zh-TW/dialog.json +5 -1
  39. package/apps/desktop/resources/locales/zh-TW/menu.json +16 -0
  40. package/apps/desktop/scripts/update-test/README.md +15 -0
  41. package/apps/desktop/src/main/core/infrastructure/BackendProxyProtocolManager.ts +7 -6
  42. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +38 -5
  43. package/apps/desktop/src/main/utils/logger.ts +2 -2
  44. package/changelog/v1.json +5 -0
  45. package/locales/en-US/auth.json +5 -0
  46. package/locales/zh-CN/auth.json +5 -0
  47. package/package.json +6 -5
  48. package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +362 -30
  49. package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +28 -4
  50. package/scripts/electronWorkflow/buildDesktopChannel.ts +135 -0
  51. package/src/app/[variants]/(main)/_layout/index.tsx +2 -0
  52. package/src/features/DesktopNavigationBridge/index.tsx +0 -9
  53. package/src/features/Electron/AuthRequiredModal/index.tsx +151 -0
  54. package/src/locales/default/auth.ts +6 -0
  55. package/src/utils/errorResponse.ts +21 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.304",
3
+ "version": "2.0.0-next.305",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -35,18 +35,19 @@
35
35
  "prebuild": "tsx scripts/prebuild.mts && npm run lint",
36
36
  "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build --webpack",
37
37
  "postbuild": "npm run build-sitemap && npm run build-migrate-db",
38
- "build-migrate-db": "bun run db:migrate",
39
- "build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
40
38
  "build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build --webpack",
41
39
  "build:docker": "npm run prebuild && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build --webpack && npm run build-sitemap",
42
40
  "build:electron": "cross-env NODE_OPTIONS=--max-old-space-size=8192 NEXT_PUBLIC_IS_DESKTOP_APP=1 tsx scripts/electronWorkflow/buildNextApp.mts",
43
41
  "build:vercel": "npm run prebuild && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack && npm run postbuild",
42
+ "build-migrate-db": "bun run db:migrate",
43
+ "build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
44
44
  "clean:node_modules": "bash -lc 'set -e; echo \"Removing all node_modules...\"; rm -rf node_modules; pnpm -r exec rm -rf node_modules; rm -rf apps/desktop/node_modules; echo \"All node_modules removed.\"'",
45
45
  "db:generate": "drizzle-kit generate && npm run workflow:dbml",
46
46
  "db:migrate": "MIGRATION_DB=1 tsx ./scripts/migrateServerDB/index.ts",
47
47
  "db:studio": "drizzle-kit studio",
48
48
  "db:visualize": "dbdocs build docs/development/database-schema.dbml --project lobe-chat",
49
49
  "desktop:build": "npm run desktop:build-next && npm run desktop:prepare-dist && npm run desktop:build-electron",
50
+ "desktop:build-channel": "tsx scripts/electronWorkflow/buildDesktopChannel.ts",
50
51
  "desktop:build-electron": "tsx scripts/electronWorkflow/buildElectron.ts",
51
52
  "desktop:build-local": "npm run desktop:build-next && npm run desktop:prepare-dist && npm run build-local --prefix=./apps/desktop",
52
53
  "desktop:build-next": "npm run build:electron",
@@ -86,11 +87,11 @@
86
87
  "start": "next start -p 3210",
87
88
  "stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
88
89
  "test": "npm run test-app && npm run test-server",
89
- "test-app": "vitest run",
90
- "test-app:coverage": "vitest --coverage --silent='passed-only'",
91
90
  "test:e2e": "pnpm --filter @lobechat/e2e-tests test",
92
91
  "test:e2e:smoke": "pnpm --filter @lobechat/e2e-tests test:smoke",
93
92
  "test:update": "vitest -u",
93
+ "test-app": "vitest run",
94
+ "test-app:coverage": "vitest --coverage --silent='passed-only'",
94
95
  "tunnel:cloudflare": "cloudflared tunnel --url http://localhost:3010",
95
96
  "tunnel:ngrok": "ngrok http http://localhost:3011",
96
97
  "type-check": "tsgo --noEmit",
@@ -344,7 +344,139 @@ export class AgentBuilderExecutionRuntime {
344
344
  }
345
345
 
346
346
  /**
347
- * Open OAuth window and wait for authentication completion
347
+ * Open OAuth window and wait for LobehubSkill authentication completion
348
+ * Returns a Promise that resolves when OAuth completes or fails
349
+ */
350
+ private openLobehubSkillOAuthWindowAndWait(
351
+ oauthUrl: string,
352
+ provider: string,
353
+ ): Promise<{ cancelled: boolean; success: boolean }> {
354
+ return new Promise((resolve) => {
355
+ // Configuration
356
+ const WINDOW_CHECK_INTERVAL_MS = 500;
357
+ const POLL_INTERVAL_MS = 1000;
358
+ const POLL_TIMEOUT_MS = 300_000; // 5 minutes timeout
359
+
360
+ let pollInterval: ReturnType<typeof setInterval> | null = null;
361
+ let pollTimeout: ReturnType<typeof setTimeout> | null = null;
362
+ let windowCheckInterval: ReturnType<typeof setInterval> | null = null;
363
+ let messageHandler: ((event: MessageEvent) => void) | null = null;
364
+ let resolved = false;
365
+
366
+ const cleanup = () => {
367
+ if (windowCheckInterval) {
368
+ clearInterval(windowCheckInterval);
369
+ windowCheckInterval = null;
370
+ }
371
+ if (pollInterval) {
372
+ clearInterval(pollInterval);
373
+ pollInterval = null;
374
+ }
375
+ if (pollTimeout) {
376
+ clearTimeout(pollTimeout);
377
+ pollTimeout = null;
378
+ }
379
+ if (messageHandler) {
380
+ window.removeEventListener('message', messageHandler);
381
+ messageHandler = null;
382
+ }
383
+ };
384
+
385
+ const resolveOnce = (result: { cancelled: boolean; success: boolean }) => {
386
+ if (resolved) return;
387
+ resolved = true;
388
+ cleanup();
389
+ resolve(result);
390
+ };
391
+
392
+ // Listen for OAuth success message from popup window
393
+ messageHandler = async (event: MessageEvent) => {
394
+ // Verify origin for security
395
+ if (event.origin !== window.location.origin) return;
396
+
397
+ if (
398
+ event.data?.type === 'LOBEHUB_SKILL_AUTH_SUCCESS' &&
399
+ event.data?.provider === provider
400
+ ) {
401
+ console.log('[LobehubSkill] OAuth success message received for provider:', provider);
402
+
403
+ // Refresh status to get the connected state
404
+ const server = await getToolStoreState().checkLobehubSkillStatus(provider);
405
+ const isConnected = server?.status === LobehubSkillStatus.CONNECTED;
406
+
407
+ resolveOnce({ cancelled: false, success: isConnected });
408
+ }
409
+ };
410
+
411
+ window.addEventListener('message', messageHandler);
412
+
413
+ // eslint-disable-next-line unicorn/consistent-function-scoping
414
+ const checkAuthStatus = async (): Promise<boolean> => {
415
+ try {
416
+ // Check LobehubSkill status
417
+ const server = await getToolStoreState().checkLobehubSkillStatus(provider);
418
+ return server?.status === LobehubSkillStatus.CONNECTED;
419
+ } catch (error) {
420
+ console.error('[LobehubSkill] Failed to check auth status:', error);
421
+ return false;
422
+ }
423
+ };
424
+
425
+ const startFallbackPolling = () => {
426
+ if (pollInterval) return;
427
+
428
+ pollInterval = setInterval(async () => {
429
+ const isConnected = await checkAuthStatus();
430
+ if (isConnected) {
431
+ resolveOnce({ cancelled: false, success: true });
432
+ }
433
+ }, POLL_INTERVAL_MS);
434
+
435
+ // Timeout after 5 minutes
436
+ pollTimeout = setTimeout(() => {
437
+ resolveOnce({ cancelled: true, success: false });
438
+ }, POLL_TIMEOUT_MS);
439
+ };
440
+
441
+ // Open OAuth window
442
+ const oauthWindow = window.open(oauthUrl, '_blank', 'width=600,height=700');
443
+
444
+ if (oauthWindow) {
445
+ // Monitor window close
446
+ windowCheckInterval = setInterval(async () => {
447
+ try {
448
+ if (oauthWindow.closed) {
449
+ if (windowCheckInterval) {
450
+ clearInterval(windowCheckInterval);
451
+ windowCheckInterval = null;
452
+ }
453
+
454
+ // Window closed, check auth status
455
+ const isConnected = await checkAuthStatus();
456
+ resolveOnce({ cancelled: !isConnected, success: isConnected });
457
+ }
458
+ } catch {
459
+ // COOP blocked window.closed access, fall back to polling
460
+ console.log(
461
+ '[LobehubSkill] COOP blocked window.closed access, falling back to polling',
462
+ );
463
+ if (windowCheckInterval) {
464
+ clearInterval(windowCheckInterval);
465
+ windowCheckInterval = null;
466
+ }
467
+ startFallbackPolling();
468
+ }
469
+ }, WINDOW_CHECK_INTERVAL_MS);
470
+ } else {
471
+ // Window was blocked, use polling
472
+ console.log('[LobehubSkill] OAuth window was blocked, falling back to polling');
473
+ startFallbackPolling();
474
+ }
475
+ });
476
+ }
477
+
478
+ /**
479
+ * Open OAuth window and wait for Klavis authentication completion
348
480
  * Returns a Promise that resolves when OAuth completes or fails
349
481
  */
350
482
  private openOAuthWindowAndWait(
@@ -777,34 +909,187 @@ export class AgentBuilderExecutionRuntime {
777
909
  success: true,
778
910
  };
779
911
  } else {
780
- // Server exists but not connected - need to reconnect
912
+ // Server exists but not connected - need to reconnect via OAuth
913
+ try {
914
+ // Get OAuth authorization URL with correct redirectUri
915
+ const redirectUri =
916
+ typeof window !== 'undefined'
917
+ ? `${window.location.origin}/oauth/callback/success?provider=${encodeURIComponent(identifier)}`
918
+ : undefined;
919
+ const authInfo = await getToolStoreState().getLobehubSkillAuthorizeUrl(
920
+ identifier,
921
+ {
922
+ redirectUri,
923
+ },
924
+ );
925
+
926
+ if (!authInfo.authorizeUrl) {
927
+ return {
928
+ content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" requires OAuth authorization but no authorization URL is available.`,
929
+ state: {
930
+ installed: false,
931
+ isLobehubSkill: true,
932
+ pluginId: identifier,
933
+ pluginName: lobehubSkillProviderInfo.label,
934
+ serverStatus: lobehubSkillServer.status,
935
+ success: false,
936
+ } as InstallPluginState,
937
+ success: false,
938
+ };
939
+ }
940
+
941
+ // Open OAuth window and wait for authorization
942
+ const authResult = await this.openLobehubSkillOAuthWindowAndWait(
943
+ authInfo.authorizeUrl,
944
+ identifier,
945
+ );
946
+
947
+ if (authResult.success) {
948
+ // OAuth successful, enable the plugin
949
+ const agentState = getAgentStoreState();
950
+ const currentPlugins =
951
+ agentSelectors.getAgentConfigById(agentId)(agentState).plugins || [];
952
+
953
+ if (!currentPlugins.includes(identifier)) {
954
+ await getAgentStoreState().optimisticUpdateAgentConfig(agentId, {
955
+ plugins: [...currentPlugins, identifier],
956
+ });
957
+ }
958
+
959
+ return {
960
+ content: `Successfully reconnected and enabled LobehubSkill provider: ${lobehubSkillProviderInfo.label}`,
961
+ state: {
962
+ installed: true,
963
+ isLobehubSkill: true,
964
+ pluginId: identifier,
965
+ pluginName: lobehubSkillProviderInfo.label,
966
+ serverStatus: 'connected',
967
+ success: true,
968
+ } as InstallPluginState,
969
+ success: true,
970
+ };
971
+ } else {
972
+ // OAuth cancelled or failed
973
+ return {
974
+ content: `OAuth authorization was cancelled or failed for LobehubSkill provider: ${lobehubSkillProviderInfo.label}. Please try again.`,
975
+ state: {
976
+ installed: false,
977
+ isLobehubSkill: true,
978
+ pluginId: identifier,
979
+ pluginName: lobehubSkillProviderInfo.label,
980
+ serverStatus: lobehubSkillServer.status,
981
+ success: false,
982
+ } as InstallPluginState,
983
+ success: false,
984
+ };
985
+ }
986
+ } catch (authError) {
987
+ const err = authError as Error;
988
+ return {
989
+ content: `Failed to reconnect LobehubSkill provider "${lobehubSkillProviderInfo.label}": ${err.message}`,
990
+ error: authError,
991
+ state: {
992
+ error: err.message,
993
+ installed: false,
994
+ isLobehubSkill: true,
995
+ pluginId: identifier,
996
+ pluginName: lobehubSkillProviderInfo.label,
997
+ serverStatus: lobehubSkillServer.status,
998
+ success: false,
999
+ } as InstallPluginState,
1000
+ success: false,
1001
+ };
1002
+ }
1003
+ }
1004
+ } else {
1005
+ // Server doesn't exist - need to initiate OAuth authorization
1006
+ try {
1007
+ // Get OAuth authorization URL with correct redirectUri
1008
+ const redirectUri =
1009
+ typeof window !== 'undefined'
1010
+ ? `${window.location.origin}/oauth/callback/success?provider=${encodeURIComponent(identifier)}`
1011
+ : undefined;
1012
+ const authInfo = await getToolStoreState().getLobehubSkillAuthorizeUrl(identifier, {
1013
+ redirectUri,
1014
+ });
1015
+
1016
+ if (!authInfo.authorizeUrl) {
1017
+ return {
1018
+ content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" requires OAuth authorization but no authorization URL is available.`,
1019
+ state: {
1020
+ installed: false,
1021
+ isLobehubSkill: true,
1022
+ pluginId: identifier,
1023
+ pluginName: lobehubSkillProviderInfo.label,
1024
+ serverStatus: 'not_connected',
1025
+ success: false,
1026
+ } as InstallPluginState,
1027
+ success: false,
1028
+ };
1029
+ }
1030
+
1031
+ // Open OAuth window and wait for authorization
1032
+ const authResult = await this.openLobehubSkillOAuthWindowAndWait(
1033
+ authInfo.authorizeUrl,
1034
+ identifier,
1035
+ );
1036
+
1037
+ if (authResult.success) {
1038
+ // OAuth successful, enable the plugin
1039
+ const agentState = getAgentStoreState();
1040
+ const currentPlugins =
1041
+ agentSelectors.getAgentConfigById(agentId)(agentState).plugins || [];
1042
+
1043
+ if (!currentPlugins.includes(identifier)) {
1044
+ await getAgentStoreState().optimisticUpdateAgentConfig(agentId, {
1045
+ plugins: [...currentPlugins, identifier],
1046
+ });
1047
+ }
1048
+
1049
+ return {
1050
+ content: `Successfully connected and enabled LobehubSkill provider: ${lobehubSkillProviderInfo.label}`,
1051
+ state: {
1052
+ installed: true,
1053
+ isLobehubSkill: true,
1054
+ pluginId: identifier,
1055
+ pluginName: lobehubSkillProviderInfo.label,
1056
+ serverStatus: 'connected',
1057
+ success: true,
1058
+ } as InstallPluginState,
1059
+ success: true,
1060
+ };
1061
+ } else {
1062
+ // OAuth cancelled or failed
1063
+ return {
1064
+ content: `OAuth authorization was cancelled or failed for LobehubSkill provider: ${lobehubSkillProviderInfo.label}. Please try again.`,
1065
+ state: {
1066
+ installed: false,
1067
+ isLobehubSkill: true,
1068
+ pluginId: identifier,
1069
+ pluginName: lobehubSkillProviderInfo.label,
1070
+ serverStatus: 'not_connected',
1071
+ success: false,
1072
+ } as InstallPluginState,
1073
+ success: false,
1074
+ };
1075
+ }
1076
+ } catch (authError) {
1077
+ const err = authError as Error;
781
1078
  return {
782
- content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" is not connected. Please reconnect it from the tools settings.`,
1079
+ content: `Failed to authorize LobehubSkill provider "${lobehubSkillProviderInfo.label}": ${err.message}`,
1080
+ error: authError,
783
1081
  state: {
1082
+ error: err.message,
784
1083
  installed: false,
785
1084
  isLobehubSkill: true,
786
1085
  pluginId: identifier,
787
1086
  pluginName: lobehubSkillProviderInfo.label,
788
- serverStatus: lobehubSkillServer.status,
1087
+ serverStatus: 'not_connected',
789
1088
  success: false,
790
1089
  } as InstallPluginState,
791
1090
  success: false,
792
1091
  };
793
1092
  }
794
- } else {
795
- // Server doesn't exist - need to connect first
796
- return {
797
- content: `LobehubSkill provider "${lobehubSkillProviderInfo.label}" is not connected. Please connect it from the tools settings first.`,
798
- state: {
799
- installed: false,
800
- isLobehubSkill: true,
801
- pluginId: identifier,
802
- pluginName: lobehubSkillProviderInfo.label,
803
- serverStatus: 'not_connected',
804
- success: false,
805
- } as InstallPluginState,
806
- success: false,
807
- };
808
1093
  }
809
1094
  }
810
1095
  }
@@ -878,18 +1163,65 @@ export class AgentBuilderExecutionRuntime {
878
1163
  };
879
1164
  }
880
1165
 
881
- // Plugin needs to be installed - return state requiring approval
882
- // The actual installation will happen when user approves
883
- return {
884
- content: `MCP plugin "${identifier}" will be installed. Please approve to continue.`,
885
- state: {
886
- awaitingApproval: true,
887
- installed: false,
888
- pluginId: identifier,
889
- success: true,
890
- } as InstallPluginState,
891
- success: true,
892
- };
1166
+ // Plugin needs to be installed - trigger actual installation from market
1167
+ try {
1168
+ // Call the store's installMCPPlugin to trigger the real installation flow
1169
+ const installSuccess = await getToolStoreState().installMCPPlugin(identifier);
1170
+
1171
+ if (installSuccess) {
1172
+ // Installation successful, enable it for the agent
1173
+ const agentState = getAgentStoreState();
1174
+ const currentPlugins =
1175
+ agentSelectors.getAgentConfigById(agentId)(agentState).plugins || [];
1176
+
1177
+ if (!currentPlugins.includes(identifier)) {
1178
+ await getAgentStoreState().optimisticUpdateAgentConfig(agentId, {
1179
+ plugins: [...currentPlugins, identifier],
1180
+ });
1181
+ }
1182
+
1183
+ // Refresh tool state to get the installed plugin info
1184
+ await getToolStoreState().refreshPlugins();
1185
+ const freshToolState = getToolStoreState();
1186
+ const installedPlugin =
1187
+ pluginSelectors.getInstalledPluginById(identifier)(freshToolState);
1188
+
1189
+ return {
1190
+ content: `Successfully installed and enabled MCP plugin "${installedPlugin?.manifest?.meta?.title || identifier}".`,
1191
+ state: {
1192
+ installed: true,
1193
+ pluginId: identifier,
1194
+ pluginName: installedPlugin?.manifest?.meta?.title || identifier,
1195
+ success: true,
1196
+ } as InstallPluginState,
1197
+ success: true,
1198
+ };
1199
+ } else {
1200
+ // Installation failed or was cancelled
1201
+ return {
1202
+ content: `Failed to install MCP plugin "${identifier}". Installation was cancelled or configuration is needed.`,
1203
+ state: {
1204
+ installed: false,
1205
+ pluginId: identifier,
1206
+ success: false,
1207
+ } as InstallPluginState,
1208
+ success: false,
1209
+ };
1210
+ }
1211
+ } catch (installError) {
1212
+ const err = installError as Error;
1213
+ return {
1214
+ content: `Failed to install MCP plugin "${identifier}": ${err.message}`,
1215
+ error: installError,
1216
+ state: {
1217
+ error: err.message,
1218
+ installed: false,
1219
+ pluginId: identifier,
1220
+ success: false,
1221
+ } as InstallPluginState,
1222
+ success: false,
1223
+ };
1224
+ }
893
1225
  } catch (error) {
894
1226
  const err = error as Error;
895
1227
  return {
@@ -12,6 +12,7 @@ import { useToolStore } from '@/store/tool';
12
12
  import {
13
13
  klavisStoreSelectors,
14
14
  lobehubSkillStoreSelectors,
15
+ mcpStoreSelectors,
15
16
  pluginSelectors,
16
17
  } from '@/store/tool/selectors';
17
18
  import { KlavisServerStatus } from '@/store/tool/slices/klavisStore/types';
@@ -44,6 +45,14 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
44
45
  lobehubSkillStoreSelectors.getServers(s).find((srv) => srv.identifier === identifier),
45
46
  );
46
47
 
48
+ // Get Market MCP plugin info
49
+ const marketPlugin = useToolStore((s) => mcpStoreSelectors.getPluginById(identifier)(s));
50
+
51
+ // Get Builtin tool info
52
+ const builtinTool = useToolStore((s) =>
53
+ s.builtinTools.find((tool) => tool.identifier === identifier),
54
+ );
55
+
47
56
  // Check if it's a Klavis tool
48
57
  const klavisTypeInfo = KLAVIS_SERVER_TYPES.find((t) => t.identifier === identifier);
49
58
  const isKlavis = source === 'official' && !!klavisTypeInfo;
@@ -166,19 +175,34 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
166
175
  );
167
176
  }
168
177
 
169
- // Render MCP marketplace plugin
178
+ // Render MCP marketplace plugin or Builtin tool
170
179
  // Note: The actual installation happens in ExecutionRuntime after user approves
180
+ const pluginName = marketPlugin?.name || builtinTool?.manifest?.meta?.title || identifier;
181
+ const pluginIcon = marketPlugin?.icon || builtinTool?.manifest?.meta?.avatar;
182
+ const pluginType = source === 'market' ? 'MCP Plugin' : 'Builtin Tool';
183
+
171
184
  return (
172
185
  <Flexbox
173
186
  gap={12}
174
187
  style={{ background: 'var(--lobe-fill-tertiary)', borderRadius: 8, padding: 16 }}
175
188
  >
176
189
  <Flexbox align="center" gap={12} horizontal>
177
- <Avatar avatar="๐Ÿ”ง" size={40} style={{ borderRadius: 8 }} />
190
+ {pluginIcon && typeof pluginIcon === 'string' && pluginIcon.startsWith('http') ? (
191
+ <Image
192
+ alt={pluginName}
193
+ height={40}
194
+ src={pluginIcon}
195
+ style={{ borderRadius: 8 }}
196
+ unoptimized
197
+ width={40}
198
+ />
199
+ ) : (
200
+ <Avatar avatar={pluginIcon || '๐Ÿ”ง'} size={40} style={{ borderRadius: 8 }} />
201
+ )}
178
202
  <Flexbox flex={1} gap={4}>
179
203
  <Flexbox align="center" gap={8} horizontal>
180
- <span style={{ fontWeight: 600 }}>{identifier}</span>
181
- <span style={{ color: 'var(--lobe-text-tertiary)', fontSize: 12 }}>MCP Plugin</span>
204
+ <span style={{ fontWeight: 600 }}>{pluginName}</span>
205
+ <span style={{ color: 'var(--lobe-text-tertiary)', fontSize: 12 }}>{pluginType}</span>
182
206
  </Flexbox>
183
207
  <span style={{ color: 'var(--lobe-text-secondary)', fontSize: 12 }}>
184
208
  {t('agentBuilder.installPlugin.clickApproveToInstall')}
@@ -0,0 +1,135 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import fs from 'fs-extra';
3
+ import { execSync } from 'node:child_process';
4
+ import path from 'node:path';
5
+
6
+ type ReleaseChannel = 'stable' | 'beta' | 'nightly';
7
+
8
+ const rootDir = path.resolve(__dirname, '../..');
9
+ const desktopDir = path.join(rootDir, 'apps/desktop');
10
+ const desktopPackageJsonPath = path.join(desktopDir, 'package.json');
11
+ const buildDir = path.join(desktopDir, 'build');
12
+
13
+ const iconTargets = ['icon.png', 'Icon.icns', 'icon.ico'];
14
+
15
+ const isFlag = (value: string) => value.startsWith('-');
16
+
17
+ const parseArgs = (args: string[]) => {
18
+ let channel = '';
19
+ let version = '';
20
+ let keepChanges = false;
21
+
22
+ for (let i = 0; i < args.length; i += 1) {
23
+ const arg = args[i];
24
+
25
+ if (arg === '--channel' || arg === '-c') {
26
+ channel = args[i + 1] ?? '';
27
+ i += 1;
28
+ continue;
29
+ }
30
+
31
+ if (arg === '--version' || arg === '-v') {
32
+ version = args[i + 1] ?? '';
33
+ i += 1;
34
+ continue;
35
+ }
36
+
37
+ if (arg === '--keep-changes') {
38
+ keepChanges = true;
39
+ continue;
40
+ }
41
+
42
+ if (!isFlag(arg)) {
43
+ if (!channel) {
44
+ channel = arg;
45
+ continue;
46
+ }
47
+
48
+ if (!version) {
49
+ version = arg;
50
+ }
51
+ }
52
+ }
53
+
54
+ return { channel, keepChanges, version };
55
+ };
56
+
57
+ const resolveDefaultVersion = () => {
58
+ const rootPackageJsonPath = path.join(rootDir, 'package.json');
59
+ const rootPackageJson = fs.readJsonSync(rootPackageJsonPath);
60
+ return rootPackageJson.version as string | undefined;
61
+ };
62
+
63
+ const backupFile = async (filePath: string) => {
64
+ try {
65
+ return await fs.readFile(filePath);
66
+ } catch {
67
+ return undefined;
68
+ }
69
+ };
70
+
71
+ const restoreFile = async (filePath: string, content?: Buffer) => {
72
+ if (!content) return;
73
+ await fs.writeFile(filePath, content);
74
+ };
75
+
76
+ const validateChannel = (channel: string): channel is ReleaseChannel =>
77
+ channel === 'stable' || channel === 'beta' || channel === 'nightly';
78
+
79
+ const runCommand = (command: string, env?: Record<string, string | undefined>) => {
80
+ execSync(command, {
81
+ cwd: rootDir,
82
+ env: { ...process.env, ...env },
83
+ stdio: 'inherit',
84
+ });
85
+ };
86
+
87
+ const main = async () => {
88
+ const { channel, version: rawVersion, keepChanges } = parseArgs(process.argv.slice(2));
89
+
90
+ if (!validateChannel(channel)) {
91
+ console.error(
92
+ 'Missing or invalid channel. Usage: npm run desktop:build-channel -- <stable|beta|nightly> [version] [--keep-changes]',
93
+ );
94
+ process.exit(1);
95
+ }
96
+
97
+ const version = rawVersion || resolveDefaultVersion();
98
+
99
+ if (!version) {
100
+ console.error('Missing version. Provide it or ensure root package.json has a version.');
101
+ process.exit(1);
102
+ }
103
+
104
+ const packageJsonBackup = await backupFile(desktopPackageJsonPath);
105
+ const iconBackups = await Promise.all(
106
+ iconTargets.map(async (fileName) => ({
107
+ content: await backupFile(path.join(buildDir, fileName)),
108
+ fileName,
109
+ })),
110
+ );
111
+
112
+ console.log(`๐Ÿšฆ CI-style build channel: ${channel}`);
113
+ console.log(`๐Ÿท๏ธ Desktop version: ${version}`);
114
+ console.log(`๐Ÿงฉ Keep local changes: ${keepChanges ? 'yes' : 'no'}`);
115
+
116
+ try {
117
+ runCommand(`npm run workflow:set-desktop-version ${version} ${channel}`);
118
+ runCommand('npm run desktop:build', { UPDATE_CHANNEL: channel });
119
+ } catch (error) {
120
+ console.error('โŒ Build failed:', error);
121
+ process.exit(1);
122
+ } finally {
123
+ if (!keepChanges) {
124
+ await restoreFile(desktopPackageJsonPath, packageJsonBackup);
125
+ await Promise.all(
126
+ iconBackups.map(({ fileName, content }) =>
127
+ restoreFile(path.join(buildDir, fileName), content),
128
+ ),
129
+ );
130
+ console.log('๐Ÿงน Restored local desktop package metadata and icons.');
131
+ }
132
+ }
133
+ };
134
+
135
+ main();
@@ -12,6 +12,7 @@ import Loading from '@/components/Loading/BrandTextLoading';
12
12
  import { isDesktop } from '@/const/version';
13
13
  import { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
14
14
  import DesktopNavigationBridge from '@/features/DesktopNavigationBridge';
15
+ import AuthRequiredModal from '@/features/Electron/AuthRequiredModal';
15
16
  import TitleBar from '@/features/Electron/titlebar/TitleBar';
16
17
  import HotkeyHelperPanel from '@/features/HotkeyHelperPanel';
17
18
  import NavPanel from '@/features/NavPanel';
@@ -45,6 +46,7 @@ const Layout: FC = () => {
45
46
  {isDesktop && <TitleBar />}
46
47
  {isDesktop && <DesktopAutoOidcOnFirstOpen />}
47
48
  {isDesktop && <DesktopNavigationBridge />}
49
+ {isDesktop && <AuthRequiredModal />}
48
50
  {showCloudPromotion && <CloudBanner />}
49
51
  </Suspense>
50
52
  <DndContextWrapper>