@lobehub/lobehub 2.0.0-next.257 → 2.0.0-next.259

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 (31) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/{electron-builder.js → electron-builder.mjs} +24 -11
  3. package/apps/desktop/electron.vite.config.ts +10 -4
  4. package/apps/desktop/native-deps.config.mjs +102 -0
  5. package/apps/desktop/package.json +8 -7
  6. package/apps/desktop/src/main/__mocks__/node-mac-permissions.ts +21 -0
  7. package/apps/desktop/src/main/__mocks__/setup.ts +8 -0
  8. package/apps/desktop/src/main/controllers/SystemCtr.ts +20 -159
  9. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +58 -90
  10. package/apps/desktop/src/main/utils/permissions.ts +307 -0
  11. package/apps/desktop/tsconfig.json +2 -1
  12. package/apps/desktop/vitest.config.mts +1 -0
  13. package/changelog/v1.json +18 -0
  14. package/locales/en-US/setting.json +1 -0
  15. package/locales/zh-CN/setting.json +1 -0
  16. package/package.json +1 -1
  17. package/packages/builtin-tool-memory/package.json +2 -1
  18. package/packages/builtin-tool-memory/src/executor/index.ts +2 -30
  19. package/packages/database/src/schemas/agentCronJob.ts +53 -19
  20. package/packages/file-loaders/package.json +1 -1
  21. package/packages/prompts/src/prompts/userMemory/__snapshots__/formatSearchResults.test.ts.snap +65 -0
  22. package/packages/prompts/src/prompts/userMemory/formatSearchResults.test.ts +200 -0
  23. package/packages/prompts/src/prompts/userMemory/formatSearchResults.ts +164 -0
  24. package/packages/prompts/src/prompts/userMemory/index.ts +2 -0
  25. package/scripts/electronWorkflow/buildNextApp.mts +39 -1
  26. package/scripts/electronWorkflow/modifiers/nextConfig.mts +26 -0
  27. package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +12 -8
  28. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +9 -7
  29. package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +0 -9
  30. package/src/features/PageEditor/Copilot/index.tsx +0 -1
  31. package/apps/desktop/src/main/utils/fullDiskAccess.ts +0 -121
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.259](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.258...v2.0.0-next.259)
6
+
7
+ <sup>Released on **2026-01-10**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Update the cron patterns fields values.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Update the cron patterns fields values, closes [#11399](https://github.com/lobehub/lobe-chat/issues/11399) ([7632cef](https://github.com/lobehub/lobe-chat/commit/7632cef))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.258](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.257...v2.0.0-next.258)
31
+
32
+ <sup>Released on **2026-01-10**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Fix memory search context.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix memory search context, closes [#11393](https://github.com/lobehub/lobe-chat/issues/11393) ([9f51a4c](https://github.com/lobehub/lobe-chat/commit/9f51a4c))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.257](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.256...v2.0.0-next.257)
6
56
 
7
57
  <sup>Released on **2026-01-10**</sup>
@@ -1,11 +1,18 @@
1
- const dotenv = require('dotenv');
2
- const fs = require('node:fs/promises');
3
- const os = require('node:os');
4
- const path = require('node:path');
1
+ import dotenv from 'dotenv';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ import { getAsarUnpackPatterns, getFilesPatterns } from './native-deps.config.mjs';
5
8
 
6
9
  dotenv.config();
7
10
 
8
- const packageJSON = require('./package.json');
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ const packageJSON = JSON.parse(
14
+ await fs.readFile(path.join(__dirname, 'package.json'), 'utf8')
15
+ );
9
16
 
10
17
  const channel = process.env.UPDATE_CHANNEL;
11
18
  const arch = os.arch();
@@ -121,22 +128,24 @@ const config = {
121
128
  artifactName: '${productName}-${version}.${ext}',
122
129
  },
123
130
  asar: true,
124
- asarUnpack: [
125
- // https://github.com/electron-userland/electron-builder/issues/9001#issuecomment-2778802044
126
- '**/node_modules/sharp/**/*',
127
- '**/node_modules/@img/**/*',
128
- ],
131
+ // Native modules must be unpacked from asar to work correctly
132
+ asarUnpack: getAsarUnpackPatterns(),
133
+
129
134
  detectUpdateChannel: true,
135
+
130
136
  directories: {
131
137
  buildResources: 'build',
132
138
  output: 'release',
133
139
  },
140
+
134
141
  dmg: {
135
142
  artifactName: '${productName}-${version}-${arch}.${ext}',
136
143
  },
144
+
137
145
  electronDownload: {
138
146
  mirror: 'https://npmmirror.com/mirrors/electron/',
139
147
  },
148
+
140
149
  files: [
141
150
  'dist',
142
151
  'resources',
@@ -147,6 +156,10 @@ const config = {
147
156
  '!dist/next/packages',
148
157
  '!dist/next/.next/server/app/sitemap',
149
158
  '!dist/next/.next/static/media',
159
+ // Exclude node_modules from packaging (except native modules)
160
+ '!node_modules',
161
+ // Include native modules (defined in native-deps.config.mjs)
162
+ ...getFilesPatterns(),
150
163
  ],
151
164
  generateUpdatesFilesForAllChannels: true,
152
165
  linux: {
@@ -220,4 +233,4 @@ const config = {
220
233
  },
221
234
  };
222
235
 
223
- module.exports = config;
236
+ export default config;
@@ -1,7 +1,9 @@
1
1
  import dotenv from 'dotenv';
2
- import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
2
+ import { defineConfig } from 'electron-vite';
3
3
  import { resolve } from 'node:path';
4
4
 
5
+ import { getExternalDependencies } from './native-deps.config.mjs';
6
+
5
7
  dotenv.config();
6
8
 
7
9
  const isDev = process.env.NODE_ENV === 'development';
@@ -13,6 +15,10 @@ export default defineConfig({
13
15
  build: {
14
16
  minify: !isDev,
15
17
  outDir: 'dist/main',
18
+ rollupOptions: {
19
+ // Native modules must be externalized to work correctly
20
+ external: getExternalDependencies(),
21
+ },
16
22
  sourcemap: isDev ? 'inline' : false,
17
23
  },
18
24
  // 这里是关键:在构建时进行文本替换
@@ -21,7 +27,7 @@ export default defineConfig({
21
27
  'process.env.OFFICIAL_CLOUD_SERVER': JSON.stringify(process.env.OFFICIAL_CLOUD_SERVER),
22
28
  'process.env.UPDATE_CHANNEL': JSON.stringify(process.env.UPDATE_CHANNEL),
23
29
  },
24
- plugins: [externalizeDepsPlugin({})],
30
+
25
31
  resolve: {
26
32
  alias: {
27
33
  '@': resolve(__dirname, 'src/main'),
@@ -35,11 +41,11 @@ export default defineConfig({
35
41
  outDir: 'dist/preload',
36
42
  sourcemap: isDev ? 'inline' : false,
37
43
  },
38
- plugins: [externalizeDepsPlugin({})],
44
+
39
45
  resolve: {
40
46
  alias: {
41
- '~common': resolve(__dirname, 'src/common'),
42
47
  '@': resolve(__dirname, 'src/main'),
48
+ '~common': resolve(__dirname, 'src/common'),
43
49
  },
44
50
  },
45
51
  },
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Native dependencies configuration for Electron build
3
+ *
4
+ * Native modules (containing .node bindings) require special handling:
5
+ * 1. Must be externalized in Vite/Rollup to prevent bundling
6
+ * 2. Must be included in electron-builder files
7
+ * 3. Must be unpacked from asar archive
8
+ *
9
+ * This module automatically resolves the full dependency tree.
10
+ */
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ /**
19
+ * List of native modules that need special handling
20
+ * Only add the top-level native modules here - dependencies are resolved automatically
21
+ */
22
+ export const nativeModules = [
23
+ 'node-mac-permissions',
24
+ // Add more native modules here as needed
25
+ // e.g., 'better-sqlite3', 'sharp', etc.
26
+ ];
27
+
28
+ /**
29
+ * Recursively resolve all dependencies of a module
30
+ * @param {string} moduleName - The module to resolve
31
+ * @param {Set<string>} visited - Set of already visited modules (to avoid cycles)
32
+ * @param {string} nodeModulesPath - Path to node_modules directory
33
+ * @returns {Set<string>} Set of all dependencies
34
+ */
35
+ function resolveDependencies(moduleName, visited = new Set(), nodeModulesPath = path.join(__dirname, 'node_modules')) {
36
+ if (visited.has(moduleName)) {
37
+ return visited;
38
+ }
39
+
40
+ const packageJsonPath = path.join(nodeModulesPath, moduleName, 'package.json');
41
+
42
+ // Check if module exists
43
+ if (!fs.existsSync(packageJsonPath)) {
44
+ return visited;
45
+ }
46
+
47
+ visited.add(moduleName);
48
+
49
+ try {
50
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
51
+ const dependencies = packageJson.dependencies || {};
52
+
53
+ for (const dep of Object.keys(dependencies)) {
54
+ resolveDependencies(dep, visited, nodeModulesPath);
55
+ }
56
+ } catch {
57
+ // Ignore errors reading package.json
58
+ }
59
+
60
+ return visited;
61
+ }
62
+
63
+ /**
64
+ * Get all dependencies for all native modules (including transitive dependencies)
65
+ * @returns {string[]} Array of all dependency names
66
+ */
67
+ export function getAllDependencies() {
68
+ const allDeps = new Set();
69
+
70
+ for (const nativeModule of nativeModules) {
71
+ const deps = resolveDependencies(nativeModule);
72
+ for (const dep of deps) {
73
+ allDeps.add(dep);
74
+ }
75
+ }
76
+
77
+ return [...allDeps];
78
+ }
79
+
80
+ /**
81
+ * Generate glob patterns for electron-builder files config
82
+ * @returns {string[]} Array of glob patterns
83
+ */
84
+ export function getFilesPatterns() {
85
+ return getAllDependencies().map((dep) => `node_modules/${dep}/**/*`);
86
+ }
87
+
88
+ /**
89
+ * Generate glob patterns for electron-builder asarUnpack config
90
+ * @returns {string[]} Array of glob patterns
91
+ */
92
+ export function getAsarUnpackPatterns() {
93
+ return getAllDependencies().map((dep) => `node_modules/${dep}/**/*`);
94
+ }
95
+
96
+ /**
97
+ * Get the list of native dependencies for Vite external config
98
+ * @returns {string[]} Array of dependency names
99
+ */
100
+ export function getExternalDependencies() {
101
+ return getAllDependencies();
102
+ }
@@ -12,11 +12,11 @@
12
12
  "main": "./dist/main/index.js",
13
13
  "scripts": {
14
14
  "build": "electron-vite build",
15
- "build-local": "npm run build && electron-builder --dir --config electron-builder.js --c.mac.notarize=false -c.mac.identity=null --c.asar=false",
16
- "build:linux": "npm run build && electron-builder --linux --config electron-builder.js --publish never",
17
- "build:mac": "npm run build && electron-builder --mac --config electron-builder.js --publish never",
18
- "build:mac:local": "npm run build && UPDATE_CHANNEL=nightly electron-builder --mac --config electron-builder.js --publish never",
19
- "build:win": "npm run build && electron-builder --win --config electron-builder.js --publish never",
15
+ "build-local": "npm run build && electron-builder --dir --config electron-builder.mjs --c.mac.notarize=false -c.mac.identity=null --c.asar=false",
16
+ "build:linux": "npm run build && electron-builder --linux --config electron-builder.mjs --publish never",
17
+ "build:mac": "npm run build && electron-builder --mac --config electron-builder.mjs --publish never",
18
+ "build:mac:local": "npm run build && UPDATE_CHANNEL=nightly electron-builder --mac --config electron-builder.mjs --publish never",
19
+ "build:win": "npm run build && electron-builder --win --config electron-builder.mjs --publish never",
20
20
  "dev": "electron-vite dev",
21
21
  "dev:static": "cross-env DESKTOP_RENDERER_STATIC=1 npm run electron:dev",
22
22
  "electron:dev": "electron-vite dev",
@@ -43,7 +43,7 @@
43
43
  "electron-window-state": "^5.0.3",
44
44
  "fetch-socks": "^1.3.2",
45
45
  "get-port-please": "^3.2.0",
46
- "pdfjs-dist": "4.10.38",
46
+ "node-mac-permissions": "^2.5.0",
47
47
  "superjson": "^2.2.6"
48
48
  },
49
49
  "devDependencies": {
@@ -102,7 +102,8 @@
102
102
  "pnpm": {
103
103
  "onlyBuiltDependencies": [
104
104
  "electron",
105
- "electron-builder"
105
+ "electron-builder",
106
+ "node-mac-permissions"
106
107
  ]
107
108
  }
108
109
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Mock for node-mac-permissions native module
3
+ * Used in tests since the native module only works on macOS
4
+ */
5
+
6
+ import { vi } from 'vitest';
7
+
8
+ export const askForAccessibilityAccess = vi.fn(() => undefined);
9
+ export const askForCalendarAccess = vi.fn(() => Promise.resolve('authorized'));
10
+ export const askForCameraAccess = vi.fn(() => Promise.resolve('authorized'));
11
+ export const askForContactsAccess = vi.fn(() => Promise.resolve('authorized'));
12
+ export const askForFoldersAccess = vi.fn(() => Promise.resolve('authorized'));
13
+ export const askForFullDiskAccess = vi.fn(() => undefined);
14
+ export const askForInputMonitoringAccess = vi.fn(() => Promise.resolve('authorized'));
15
+ export const askForLocationAccess = vi.fn(() => Promise.resolve('authorized'));
16
+ export const askForMicrophoneAccess = vi.fn(() => Promise.resolve('authorized'));
17
+ export const askForPhotosAccess = vi.fn(() => Promise.resolve('authorized'));
18
+ export const askForRemindersAccess = vi.fn(() => Promise.resolve('authorized'));
19
+ export const askForSpeechRecognitionAccess = vi.fn(() => Promise.resolve('authorized'));
20
+ export const askForScreenCaptureAccess = vi.fn(() => undefined);
21
+ export const getAuthStatus = vi.fn(() => 'authorized');
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Vitest setup file for mocking native modules
3
+ */
4
+
5
+ import { vi } from 'vitest';
6
+
7
+ // Mock node-mac-permissions before any imports
8
+ vi.mock('node-mac-permissions', () => import('./node-mac-permissions'));
@@ -1,10 +1,18 @@
1
1
  import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
2
- import { app, desktopCapturer, dialog, nativeTheme, shell, systemPreferences } from 'electron';
2
+ import { app, dialog, nativeTheme, shell } from 'electron';
3
3
  import { macOS } from 'electron-is';
4
4
  import process from 'node:process';
5
5
 
6
- import { checkFullDiskAccess, openFullDiskAccessSettings } from '@/utils/fullDiskAccess';
7
6
  import { createLogger } from '@/utils/logger';
7
+ import {
8
+ getAccessibilityStatus,
9
+ getFullDiskAccessStatus,
10
+ getMediaAccessStatus,
11
+ openFullDiskAccessSettings,
12
+ requestAccessibilityAccess,
13
+ requestMicrophoneAccess,
14
+ requestScreenCaptureAccess,
15
+ } from '@/utils/permissions';
8
16
 
9
17
  import { ControllerModule, IpcMethod } from './index';
10
18
 
@@ -55,43 +63,23 @@ export default class SystemController extends ControllerModule {
55
63
 
56
64
  @IpcMethod()
57
65
  requestAccessibilityAccess() {
58
- if (!macOS()) {
59
- logger.info('[Accessibility] Not macOS, returning true');
60
- return true;
61
- }
62
- logger.info('[Accessibility] Requesting accessibility access (will prompt if not granted)...');
63
- // Pass true to prompt user if not already trusted
64
- const result = systemPreferences.isTrustedAccessibilityClient(true);
65
- logger.info(`[Accessibility] isTrustedAccessibilityClient(true) returned: ${result}`);
66
- return result;
66
+ return requestAccessibilityAccess();
67
67
  }
68
68
 
69
69
  @IpcMethod()
70
70
  getAccessibilityStatus() {
71
- if (!macOS()) {
72
- logger.info('[Accessibility] Not macOS, returning true');
73
- return true;
74
- }
75
- // Pass false to just check without prompting
76
- const status = systemPreferences.isTrustedAccessibilityClient(false);
77
- logger.info(`[Accessibility] Current status: ${status}`);
78
- return status;
71
+ const status = getAccessibilityStatus();
72
+ return status === 'granted';
79
73
  }
80
74
 
81
- /**
82
- * Check if Full Disk Access is granted.
83
- * This works by attempting to read a protected system directory.
84
- * Calling this also registers the app in the TCC database, making it appear
85
- * in System Settings > Privacy & Security > Full Disk Access.
86
- */
87
75
  @IpcMethod()
88
76
  getFullDiskAccessStatus(): boolean {
89
- return checkFullDiskAccess();
77
+ const status = getFullDiskAccessStatus();
78
+ return status === 'granted';
90
79
  }
91
80
 
92
81
  /**
93
82
  * Prompt the user with a native dialog if Full Disk Access is not granted.
94
- * Based on https://github.com/inket/FullDiskAccess
95
83
  *
96
84
  * @param options - Dialog options
97
85
  * @returns 'granted' if already granted, 'opened_settings' if user chose to open settings,
@@ -105,9 +93,9 @@ export default class SystemController extends ControllerModule {
105
93
  title?: string;
106
94
  }): Promise<'cancelled' | 'granted' | 'opened_settings' | 'skipped'> {
107
95
  // Check if already granted
108
- if (checkFullDiskAccess()) {
96
+ const status = getFullDiskAccessStatus();
97
+ if (status === 'granted') {
109
98
  logger.info('[FullDiskAccess] Already granted, skipping prompt');
110
-
111
99
  return 'granted';
112
100
  }
113
101
 
@@ -151,132 +139,19 @@ export default class SystemController extends ControllerModule {
151
139
 
152
140
  @IpcMethod()
153
141
  async getMediaAccessStatus(mediaType: 'microphone' | 'screen'): Promise<string> {
154
- if (!macOS()) return 'granted';
155
- return systemPreferences.getMediaAccessStatus(mediaType);
142
+ return getMediaAccessStatus(mediaType);
156
143
  }
157
144
 
158
145
  @IpcMethod()
159
146
  async requestMicrophoneAccess(): Promise<boolean> {
160
- if (!macOS()) {
161
- logger.info('[Microphone] Not macOS, returning true');
162
- return true;
163
- }
164
-
165
- const status = systemPreferences.getMediaAccessStatus('microphone');
166
- logger.info(`[Microphone] Current status: ${status}`);
167
-
168
- // Only ask for access if status is 'not-determined'
169
- // If already denied/restricted, the system won't show a prompt
170
- if (status === 'not-determined') {
171
- logger.info('[Microphone] Status is not-determined, calling askForMediaAccess...');
172
- try {
173
- const result = await systemPreferences.askForMediaAccess('microphone');
174
- logger.info(`[Microphone] askForMediaAccess result: ${result}`);
175
- return result;
176
- } catch (error) {
177
- logger.error('[Microphone] askForMediaAccess failed:', error);
178
- return false;
179
- }
180
- }
181
-
182
- if (status === 'granted') {
183
- logger.info('[Microphone] Already granted');
184
- return true;
185
- }
186
-
187
- // If denied or restricted, open System Settings for manual enable
188
- logger.info(`[Microphone] Status is ${status}, opening System Settings...`);
189
- await shell.openExternal(
190
- 'x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone',
191
- );
192
-
193
- return false;
147
+ return requestMicrophoneAccess();
194
148
  }
195
149
 
196
150
  @IpcMethod()
197
151
  async requestScreenAccess(): Promise<boolean> {
198
- if (!macOS()) {
199
- logger.info('[Screen] Not macOS, returning true');
200
- return true;
201
- }
202
-
203
- const status = systemPreferences.getMediaAccessStatus('screen');
204
- logger.info(`[Screen] Current status: ${status}`);
205
-
206
- // If already granted, no need to do anything
207
- if (status === 'granted') {
208
- logger.info('[Screen] Already granted');
209
- return true;
210
- }
211
-
212
- // IMPORTANT:
213
- // On macOS, the app may NOT appear in "Screen Recording" list until it actually
214
- // requests the permission once (TCC needs to register this app).
215
- // We use multiple approaches to ensure TCC registration:
216
- // 1. desktopCapturer.getSources() in main process
217
- // 2. getDisplayMedia() in renderer as fallback
218
-
219
- // Approach 1: Use desktopCapturer in main process
220
- logger.info('[Screen] Attempting TCC registration via desktopCapturer.getSources...');
221
- try {
222
- // Using a reasonable thumbnail size and both types to ensure TCC registration
223
- const sources = await desktopCapturer.getSources({
224
- fetchWindowIcons: true,
225
- thumbnailSize: { height: 144, width: 256 },
226
- types: ['screen', 'window'],
227
- });
228
- // Access the sources to ensure the capture actually happens
229
- logger.info(`[Screen] desktopCapturer.getSources returned ${sources.length} sources`);
230
- } catch (error) {
231
- logger.warn('[Screen] desktopCapturer.getSources failed:', error);
232
- }
233
-
234
- // Approach 2: Trigger getDisplayMedia in renderer as additional attempt
235
- // This shows the OS capture picker which definitely registers with TCC
236
- logger.info('[Screen] Attempting TCC registration via getDisplayMedia in renderer...');
237
- try {
238
- const mainWindow = this.app.browserManager.getMainWindow()?.browserWindow;
239
- if (mainWindow && !mainWindow.isDestroyed()) {
240
- const script = `
241
- (async () => {
242
- try {
243
- const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
244
- stream.getTracks().forEach(t => t.stop());
245
- return true;
246
- } catch (e) {
247
- console.error('[Screen] getDisplayMedia error:', e);
248
- return false;
249
- }
250
- })()
251
- `.trim();
252
-
253
- const result = await mainWindow.webContents.executeJavaScript(script, true);
254
- logger.info(`[Screen] getDisplayMedia result: ${result}`);
255
- } else {
256
- logger.warn('[Screen] Main window not available for getDisplayMedia');
257
- }
258
- } catch (error) {
259
- logger.warn('[Screen] getDisplayMedia failed:', error);
260
- }
261
-
262
- // Check status after attempts
263
- const newStatus = systemPreferences.getMediaAccessStatus('screen');
264
- logger.info(`[Screen] Status after TCC attempts: ${newStatus}`);
265
-
266
- // Open System Settings for user to manually enable screen recording
267
- logger.info('[Screen] Opening System Settings for Screen Recording...');
268
- await shell.openExternal(
269
- 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture',
270
- );
271
-
272
- const finalStatus = systemPreferences.getMediaAccessStatus('screen');
273
- logger.info(`[Screen] Final status: ${finalStatus}`);
274
- return finalStatus === 'granted';
152
+ return requestScreenCaptureAccess();
275
153
  }
276
154
 
277
- /**
278
- * Open Full Disk Access settings page
279
- */
280
155
  @IpcMethod()
281
156
  async openFullDiskAccessSettings() {
282
157
  return openFullDiskAccessSettings();
@@ -287,9 +162,6 @@ export default class SystemController extends ControllerModule {
287
162
  return shell.openExternal(url);
288
163
  }
289
164
 
290
- /**
291
- * Open native folder picker dialog
292
- */
293
165
  @IpcMethod()
294
166
  async selectFolder(payload?: {
295
167
  defaultPath?: string;
@@ -310,23 +182,15 @@ export default class SystemController extends ControllerModule {
310
182
  return result.filePaths[0];
311
183
  }
312
184
 
313
- /**
314
- * Get the OS system locale
315
- */
316
185
  @IpcMethod()
317
186
  getSystemLocale(): string {
318
187
  return app.getLocale();
319
188
  }
320
189
 
321
- /**
322
- * 更新应用语言设置
323
- */
324
190
  @IpcMethod()
325
191
  async updateLocale(locale: string) {
326
- // 保存语言设置
327
192
  this.app.storeManager.set('locale', locale);
328
193
 
329
- // 更新i18n实例的语言
330
194
  await this.app.i18n.changeLanguage(locale === 'auto' ? app.getLocale() : locale);
331
195
  this.app.browserManager.broadcastToAllWindows('localeChanged', { locale });
332
196
 
@@ -354,9 +218,6 @@ export default class SystemController extends ControllerModule {
354
218
  nativeTheme.themeSource = themeMode;
355
219
  }
356
220
 
357
- /**
358
- * Initialize system theme listener to monitor OS theme changes
359
- */
360
221
  private initializeSystemThemeListener() {
361
222
  if (this.systemThemeListenerInitialized) {
362
223
  logger.debug('System theme listener already initialized');