@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.
- package/CHANGELOG.md +50 -0
- package/apps/desktop/{electron-builder.js → electron-builder.mjs} +24 -11
- package/apps/desktop/electron.vite.config.ts +10 -4
- package/apps/desktop/native-deps.config.mjs +102 -0
- package/apps/desktop/package.json +8 -7
- package/apps/desktop/src/main/__mocks__/node-mac-permissions.ts +21 -0
- package/apps/desktop/src/main/__mocks__/setup.ts +8 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +20 -159
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +58 -90
- package/apps/desktop/src/main/utils/permissions.ts +307 -0
- package/apps/desktop/tsconfig.json +2 -1
- package/apps/desktop/vitest.config.mts +1 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/setting.json +1 -0
- package/locales/zh-CN/setting.json +1 -0
- package/package.json +1 -1
- package/packages/builtin-tool-memory/package.json +2 -1
- package/packages/builtin-tool-memory/src/executor/index.ts +2 -30
- package/packages/database/src/schemas/agentCronJob.ts +53 -19
- package/packages/file-loaders/package.json +1 -1
- package/packages/prompts/src/prompts/userMemory/__snapshots__/formatSearchResults.test.ts.snap +65 -0
- package/packages/prompts/src/prompts/userMemory/formatSearchResults.test.ts +200 -0
- package/packages/prompts/src/prompts/userMemory/formatSearchResults.ts +164 -0
- package/packages/prompts/src/prompts/userMemory/index.ts +2 -0
- package/scripts/electronWorkflow/buildNextApp.mts +39 -1
- package/scripts/electronWorkflow/modifiers/nextConfig.mts +26 -0
- package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +12 -8
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +9 -7
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +0 -9
- package/src/features/PageEditor/Copilot/index.tsx +0 -1
- 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
|
+
[](#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
|
+
[](#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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
236
|
+
export default config;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
|
-
import { defineConfig
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
16
|
-
"build:linux": "npm run build && electron-builder --linux --config electron-builder.
|
|
17
|
-
"build:mac": "npm run build && electron-builder --mac --config electron-builder.
|
|
18
|
-
"build:mac:local": "npm run build && UPDATE_CHANNEL=nightly electron-builder --mac --config electron-builder.
|
|
19
|
-
"build:win": "npm run build && electron-builder --win --config electron-builder.
|
|
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
|
-
"
|
|
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');
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
|
|
2
|
-
import { app,
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
return systemPreferences.getMediaAccessStatus(mediaType);
|
|
142
|
+
return getMediaAccessStatus(mediaType);
|
|
156
143
|
}
|
|
157
144
|
|
|
158
145
|
@IpcMethod()
|
|
159
146
|
async requestMicrophoneAccess(): Promise<boolean> {
|
|
160
|
-
|
|
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
|
-
|
|
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');
|