@lobehub/lobehub 2.0.0-next.264 → 2.0.0-next.265
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/.github/workflows/manual-build-desktop.yml +16 -37
- package/CHANGELOG.md +27 -0
- package/apps/desktop/native-deps.config.mjs +19 -3
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +13 -0
- package/apps/desktop/src/main/utils/permissions.ts +86 -22
- package/changelog/v1.json +9 -0
- package/package.json +2 -2
- package/packages/database/src/models/__tests__/agent.test.ts +165 -4
- package/packages/database/src/models/agent.ts +46 -0
- package/packages/database/src/repositories/agentGroup/index.test.ts +498 -0
- package/packages/database/src/repositories/agentGroup/index.ts +150 -0
- package/packages/database/src/repositories/home/__tests__/index.test.ts +113 -1
- package/packages/database/src/repositories/home/index.ts +48 -67
- package/pnpm-workspace.yaml +1 -0
- package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +2 -6
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +100 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -4
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/useDropdownMenu.tsx +149 -0
- package/src/app/[variants]/(main)/home/_layout/hooks/index.ts +0 -1
- package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +1 -1
- package/src/features/ChatInput/InputEditor/index.tsx +1 -0
- package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
- package/src/server/routers/lambda/agent.ts +15 -0
- package/src/server/routers/lambda/agentGroup.ts +16 -0
- package/src/services/agent.ts +11 -0
- package/src/services/chatGroup/index.ts +11 -0
- package/src/store/home/slices/sidebarUI/action.test.ts +23 -22
- package/src/store/home/slices/sidebarUI/action.ts +37 -9
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +0 -62
- package/src/app/[variants]/(main)/home/_layout/hooks/useSessionItemMenuItems.tsx +0 -238
|
@@ -44,28 +44,6 @@ env:
|
|
|
44
44
|
BUN_VERSION: 1.2.23
|
|
45
45
|
|
|
46
46
|
jobs:
|
|
47
|
-
test:
|
|
48
|
-
name: Code quality check
|
|
49
|
-
runs-on: ubuntu-latest
|
|
50
|
-
steps:
|
|
51
|
-
- name: Checkout base
|
|
52
|
-
uses: actions/checkout@v6
|
|
53
|
-
with:
|
|
54
|
-
fetch-depth: 0
|
|
55
|
-
|
|
56
|
-
- name: Setup Node & Bun
|
|
57
|
-
uses: ./.github/actions/setup-node-bun
|
|
58
|
-
with:
|
|
59
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
60
|
-
bun-version: ${{ env.BUN_VERSION }}
|
|
61
|
-
package-manager-cache: 'false'
|
|
62
|
-
|
|
63
|
-
- name: Install deps
|
|
64
|
-
run: bun i
|
|
65
|
-
|
|
66
|
-
- name: Lint
|
|
67
|
-
run: bun run lint
|
|
68
|
-
|
|
69
47
|
version:
|
|
70
48
|
name: Determine version
|
|
71
49
|
runs-on: ubuntu-latest
|
|
@@ -106,7 +84,7 @@ jobs:
|
|
|
106
84
|
echo "🚦 Release Version: ${{ steps.set_version.outputs.version }}"
|
|
107
85
|
|
|
108
86
|
build-macos:
|
|
109
|
-
needs: [version
|
|
87
|
+
needs: [version]
|
|
110
88
|
name: Build Desktop App (macOS)
|
|
111
89
|
if: inputs.build_macos
|
|
112
90
|
runs-on: ${{ matrix.os }}
|
|
@@ -126,10 +104,10 @@ jobs:
|
|
|
126
104
|
|
|
127
105
|
# node-linker=hoisted 模式将可以确保 asar 压缩可用
|
|
128
106
|
- name: Install dependencies
|
|
129
|
-
run:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
107
|
+
run: |
|
|
108
|
+
pnpm install --node-linker=hoisted &
|
|
109
|
+
npm run install-isolated --prefix=./apps/desktop &
|
|
110
|
+
wait
|
|
133
111
|
|
|
134
112
|
- name: Set package version
|
|
135
113
|
run: npm run workflow:set-desktop-version ${{ needs.version.outputs.version }} ${{ inputs.channel }}
|
|
@@ -187,7 +165,7 @@ jobs:
|
|
|
187
165
|
retention-days: 5
|
|
188
166
|
|
|
189
167
|
build-windows:
|
|
190
|
-
needs: [version
|
|
168
|
+
needs: [version]
|
|
191
169
|
name: Build Desktop App (Windows)
|
|
192
170
|
if: inputs.build_windows
|
|
193
171
|
runs-on: windows-2025
|
|
@@ -203,10 +181,11 @@ jobs:
|
|
|
203
181
|
package-manager-cache: 'false'
|
|
204
182
|
|
|
205
183
|
- name: Install dependencies
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
184
|
+
shell: pwsh
|
|
185
|
+
run: |
|
|
186
|
+
$job1 = Start-Job -ScriptBlock { pnpm install --node-linker=hoisted }
|
|
187
|
+
$job2 = Start-Job -ScriptBlock { npm run install-isolated --prefix=./apps/desktop }
|
|
188
|
+
$job1, $job2 | Wait-Job | Receive-Job
|
|
210
189
|
|
|
211
190
|
- name: Set package version
|
|
212
191
|
run: npm run workflow:set-desktop-version ${{ needs.version.outputs.version }} ${{ inputs.channel }}
|
|
@@ -240,7 +219,7 @@ jobs:
|
|
|
240
219
|
retention-days: 5
|
|
241
220
|
|
|
242
221
|
build-linux:
|
|
243
|
-
needs: [version
|
|
222
|
+
needs: [version]
|
|
244
223
|
name: Build Desktop App (Linux)
|
|
245
224
|
if: inputs.build_linux
|
|
246
225
|
runs-on: ubuntu-latest
|
|
@@ -256,10 +235,10 @@ jobs:
|
|
|
256
235
|
package-manager-cache: 'false'
|
|
257
236
|
|
|
258
237
|
- name: Install dependencies
|
|
259
|
-
run:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
238
|
+
run: |
|
|
239
|
+
pnpm install --node-linker=hoisted &
|
|
240
|
+
npm run install-isolated --prefix=./apps/desktop &
|
|
241
|
+
wait
|
|
263
242
|
|
|
264
243
|
- name: Set package version
|
|
265
244
|
run: npm run workflow:set-desktop-version ${{ needs.version.outputs.version }} ${{ inputs.channel }}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.265](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.264...v2.0.0-next.265)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-11**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix duplicate agent and group, Fix Windows desktop build error with macOS native module, force plain text paste in ChatInput editor.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix duplicate agent and group, closes [#11411](https://github.com/lobehub/lobe-chat/issues/11411) ([bc8aea4](https://github.com/lobehub/lobe-chat/commit/bc8aea4))
|
|
21
|
+
- **misc**: Fix Windows desktop build error with macOS native module, closes [#11417](https://github.com/lobehub/lobe-chat/issues/11417) ([67a8114](https://github.com/lobehub/lobe-chat/commit/67a8114))
|
|
22
|
+
- **misc**: Force plain text paste in ChatInput editor, closes [#11414](https://github.com/lobehub/lobe-chat/issues/11414) ([70daf13](https://github.com/lobehub/lobe-chat/commit/70daf13))
|
|
23
|
+
|
|
24
|
+
</details>
|
|
25
|
+
|
|
26
|
+
<div align="right">
|
|
27
|
+
|
|
28
|
+
[](#readme-top)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
5
32
|
## [Version 2.0.0-next.264](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.263...v2.0.0-next.264)
|
|
6
33
|
|
|
7
34
|
<sup>Released on **2026-01-11**</sup>
|
|
@@ -8,19 +8,31 @@
|
|
|
8
8
|
*
|
|
9
9
|
* This module automatically resolves the full dependency tree.
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
11
|
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Get the current target platform
|
|
20
|
+
* During build, electron-builder sets npm_config_platform
|
|
21
|
+
* Falls back to os.platform() for development
|
|
22
|
+
*/
|
|
23
|
+
function getTargetPlatform() {
|
|
24
|
+
return process.env.npm_config_platform || os.platform();
|
|
25
|
+
}
|
|
26
|
+
const isDarwin = getTargetPlatform() === 'darwin';
|
|
18
27
|
/**
|
|
19
28
|
* List of native modules that need special handling
|
|
20
29
|
* Only add the top-level native modules here - dependencies are resolved automatically
|
|
30
|
+
*
|
|
31
|
+
* Platform-specific modules are only included when building for their target platform
|
|
21
32
|
*/
|
|
22
33
|
export const nativeModules = [
|
|
23
|
-
|
|
34
|
+
// macOS-only native modules
|
|
35
|
+
...(isDarwin ? ['node-mac-permissions'] : []),
|
|
24
36
|
// Add more native modules here as needed
|
|
25
37
|
// e.g., 'better-sqlite3', 'sharp', etc.
|
|
26
38
|
];
|
|
@@ -32,7 +44,11 @@ export const nativeModules = [
|
|
|
32
44
|
* @param {string} nodeModulesPath - Path to node_modules directory
|
|
33
45
|
* @returns {Set<string>} Set of all dependencies
|
|
34
46
|
*/
|
|
35
|
-
function resolveDependencies(
|
|
47
|
+
function resolveDependencies(
|
|
48
|
+
moduleName,
|
|
49
|
+
visited = new Set(),
|
|
50
|
+
nodeModulesPath = path.join(__dirname, 'node_modules'),
|
|
51
|
+
) {
|
|
36
52
|
if (visited.has(moduleName)) {
|
|
37
53
|
return visited;
|
|
38
54
|
}
|
|
@@ -4,6 +4,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
4
4
|
import type { App } from '@/core/App';
|
|
5
5
|
import type { IpcContext } from '@/utils/ipc';
|
|
6
6
|
import { IpcHandler } from '@/utils/ipc/base';
|
|
7
|
+
import {
|
|
8
|
+
__resetMacPermissionsModuleCache,
|
|
9
|
+
__setMacPermissionsModule,
|
|
10
|
+
} from '@/utils/permissions';
|
|
7
11
|
|
|
8
12
|
import SystemController from '../SystemCtr';
|
|
9
13
|
|
|
@@ -131,6 +135,9 @@ describe('SystemController', () => {
|
|
|
131
135
|
ipcHandlers.clear();
|
|
132
136
|
ipcMainHandleMock.mockClear();
|
|
133
137
|
(IpcHandler.getInstance() as any).registeredChannels?.clear();
|
|
138
|
+
// Reset and inject mock permissions module for testing
|
|
139
|
+
__resetMacPermissionsModuleCache();
|
|
140
|
+
__setMacPermissionsModule(permissionsMock as any);
|
|
134
141
|
controller = new SystemController(mockApp);
|
|
135
142
|
});
|
|
136
143
|
|
|
@@ -169,6 +176,8 @@ describe('SystemController', () => {
|
|
|
169
176
|
it('should return true on non-macOS when requesting accessibility access', async () => {
|
|
170
177
|
const { macOS } = await import('electron-is');
|
|
171
178
|
vi.mocked(macOS).mockReturnValue(false);
|
|
179
|
+
// Clear the injected module to simulate non-macOS behavior
|
|
180
|
+
__setMacPermissionsModule(null);
|
|
172
181
|
|
|
173
182
|
const result = await invokeIpc('system.requestAccessibilityAccess');
|
|
174
183
|
|
|
@@ -177,6 +186,7 @@ describe('SystemController', () => {
|
|
|
177
186
|
|
|
178
187
|
// Reset
|
|
179
188
|
vi.mocked(macOS).mockReturnValue(true);
|
|
189
|
+
__setMacPermissionsModule(permissionsMock as any);
|
|
180
190
|
});
|
|
181
191
|
});
|
|
182
192
|
|
|
@@ -226,6 +236,8 @@ describe('SystemController', () => {
|
|
|
226
236
|
const { macOS } = await import('electron-is');
|
|
227
237
|
const { shell } = await import('electron');
|
|
228
238
|
vi.mocked(macOS).mockReturnValue(false);
|
|
239
|
+
// Clear the injected module to simulate non-macOS behavior
|
|
240
|
+
__setMacPermissionsModule(null);
|
|
229
241
|
|
|
230
242
|
const result = await invokeIpc('system.requestMicrophoneAccess');
|
|
231
243
|
|
|
@@ -235,6 +247,7 @@ describe('SystemController', () => {
|
|
|
235
247
|
|
|
236
248
|
// Reset
|
|
237
249
|
vi.mocked(macOS).mockReturnValue(true);
|
|
250
|
+
__setMacPermissionsModule(permissionsMock as any);
|
|
238
251
|
});
|
|
239
252
|
});
|
|
240
253
|
|
|
@@ -1,22 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified macOS Permission Management using node-mac-permissions
|
|
3
3
|
* @see https://github.com/codebytere/node-mac-permissions
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: node-mac-permissions is a macOS-only native module.
|
|
6
|
+
* It must be dynamically imported to prevent loading errors on Windows/Linux.
|
|
4
7
|
*/
|
|
5
8
|
import { shell } from 'electron';
|
|
6
9
|
import { macOS } from 'electron-is';
|
|
7
|
-
import {
|
|
8
|
-
askForAccessibilityAccess,
|
|
9
|
-
askForCameraAccess,
|
|
10
|
-
askForFullDiskAccess,
|
|
11
|
-
askForMicrophoneAccess,
|
|
12
|
-
askForScreenCaptureAccess,
|
|
13
|
-
getAuthStatus,
|
|
14
|
-
type AuthType,
|
|
15
|
-
type PermissionType,
|
|
16
|
-
} from 'node-mac-permissions';
|
|
17
10
|
|
|
18
11
|
import { createLogger } from './logger';
|
|
19
12
|
|
|
13
|
+
// Type definitions - use module types when available, fallback to local definition
|
|
14
|
+
// Note: We don't import the module statically, so we need local type definitions
|
|
15
|
+
type AuthType =
|
|
16
|
+
| 'accessibility'
|
|
17
|
+
| 'calendar'
|
|
18
|
+
| 'camera'
|
|
19
|
+
| 'contacts'
|
|
20
|
+
| 'full-disk-access'
|
|
21
|
+
| 'input-monitoring'
|
|
22
|
+
| 'location'
|
|
23
|
+
| 'microphone'
|
|
24
|
+
| 'reminders'
|
|
25
|
+
| 'screen'
|
|
26
|
+
| 'speech-recognition';
|
|
27
|
+
|
|
28
|
+
type PermissionType = 'authorized' | 'denied' | 'not determined' | 'restricted';
|
|
29
|
+
|
|
30
|
+
// Lazy-loaded module cache
|
|
31
|
+
let macPermissionsModule: typeof import('node-mac-permissions') | null = null;
|
|
32
|
+
|
|
33
|
+
// Test injection override (set via __setMacPermissionsModule for testing)
|
|
34
|
+
let testModuleOverride: typeof import('node-mac-permissions') | null = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Lazily load the node-mac-permissions module (macOS only)
|
|
38
|
+
* Returns null on non-macOS platforms
|
|
39
|
+
*/
|
|
40
|
+
function getMacPermissionsModule(): typeof import('node-mac-permissions') | null {
|
|
41
|
+
// Allow test injection to override the module
|
|
42
|
+
if (testModuleOverride) {
|
|
43
|
+
return testModuleOverride;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!macOS()) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!macPermissionsModule) {
|
|
51
|
+
// Dynamic require to prevent module loading on non-macOS platforms
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
+
macPermissionsModule = require('node-mac-permissions');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return macPermissionsModule;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reset the module cache (for testing purposes)
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export function __resetMacPermissionsModuleCache(): void {
|
|
64
|
+
macPermissionsModule = null;
|
|
65
|
+
testModuleOverride = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set the mac permissions module (for testing purposes)
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export function __setMacPermissionsModule(
|
|
73
|
+
module: typeof import('node-mac-permissions') | null,
|
|
74
|
+
): void {
|
|
75
|
+
testModuleOverride = module;
|
|
76
|
+
}
|
|
77
|
+
|
|
20
78
|
const logger = createLogger('utils:permissions');
|
|
21
79
|
|
|
22
80
|
/**
|
|
@@ -42,12 +100,13 @@ function normalizeStatus(status: PermissionType | 'not determined'): PermissionS
|
|
|
42
100
|
* Get the authorization status for a specific permission type
|
|
43
101
|
*/
|
|
44
102
|
export function getPermissionStatus(type: AuthType): PermissionStatus {
|
|
45
|
-
|
|
103
|
+
const macPermissions = getMacPermissionsModule();
|
|
104
|
+
if (!macPermissions) {
|
|
46
105
|
logger.debug(`[Permission] Not macOS, returning granted for ${type}`);
|
|
47
106
|
return 'granted';
|
|
48
107
|
}
|
|
49
108
|
|
|
50
|
-
const status = getAuthStatus(type);
|
|
109
|
+
const status = macPermissions.getAuthStatus(type);
|
|
51
110
|
const normalized = normalizeStatus(status);
|
|
52
111
|
logger.info(`[Permission] ${type} status: ${normalized}`);
|
|
53
112
|
return normalized;
|
|
@@ -65,13 +124,14 @@ export function getAccessibilityStatus(): PermissionStatus {
|
|
|
65
124
|
* Opens System Preferences to the Accessibility pane
|
|
66
125
|
*/
|
|
67
126
|
export function requestAccessibilityAccess(): boolean {
|
|
68
|
-
|
|
127
|
+
const macPermissions = getMacPermissionsModule();
|
|
128
|
+
if (!macPermissions) {
|
|
69
129
|
logger.info('[Accessibility] Not macOS, returning true');
|
|
70
130
|
return true;
|
|
71
131
|
}
|
|
72
132
|
|
|
73
133
|
logger.info('[Accessibility] Requesting accessibility access...');
|
|
74
|
-
askForAccessibilityAccess();
|
|
134
|
+
macPermissions.askForAccessibilityAccess();
|
|
75
135
|
|
|
76
136
|
// Check the status after requesting
|
|
77
137
|
const status = getPermissionStatus('accessibility');
|
|
@@ -90,7 +150,8 @@ export function getMicrophoneStatus(): PermissionStatus {
|
|
|
90
150
|
* Shows the system permission dialog if not determined
|
|
91
151
|
*/
|
|
92
152
|
export async function requestMicrophoneAccess(): Promise<boolean> {
|
|
93
|
-
|
|
153
|
+
const macPermissions = getMacPermissionsModule();
|
|
154
|
+
if (!macPermissions) {
|
|
94
155
|
logger.info('[Microphone] Not macOS, returning true');
|
|
95
156
|
return true;
|
|
96
157
|
}
|
|
@@ -106,7 +167,7 @@ export async function requestMicrophoneAccess(): Promise<boolean> {
|
|
|
106
167
|
if (currentStatus === 'not-determined') {
|
|
107
168
|
logger.info('[Microphone] Status is not-determined, requesting access...');
|
|
108
169
|
try {
|
|
109
|
-
const result = await askForMicrophoneAccess();
|
|
170
|
+
const result = await macPermissions.askForMicrophoneAccess();
|
|
110
171
|
logger.info(`[Microphone] askForMicrophoneAccess result: ${result}`);
|
|
111
172
|
return result === 'authorized';
|
|
112
173
|
} catch (error) {
|
|
@@ -135,7 +196,8 @@ export function getCameraStatus(): PermissionStatus {
|
|
|
135
196
|
* Shows the system permission dialog if not determined
|
|
136
197
|
*/
|
|
137
198
|
export async function requestCameraAccess(): Promise<boolean> {
|
|
138
|
-
|
|
199
|
+
const macPermissions = getMacPermissionsModule();
|
|
200
|
+
if (!macPermissions) {
|
|
139
201
|
logger.info('[Camera] Not macOS, returning true');
|
|
140
202
|
return true;
|
|
141
203
|
}
|
|
@@ -151,7 +213,7 @@ export async function requestCameraAccess(): Promise<boolean> {
|
|
|
151
213
|
if (currentStatus === 'not-determined') {
|
|
152
214
|
logger.info('[Camera] Status is not-determined, requesting access...');
|
|
153
215
|
try {
|
|
154
|
-
const result = await askForCameraAccess();
|
|
216
|
+
const result = await macPermissions.askForCameraAccess();
|
|
155
217
|
logger.info(`[Camera] askForCameraAccess result: ${result}`);
|
|
156
218
|
return result === 'authorized';
|
|
157
219
|
} catch (error) {
|
|
@@ -181,7 +243,8 @@ export function getScreenCaptureStatus(): PermissionStatus {
|
|
|
181
243
|
* @param openPreferences - Whether to open System Preferences (default: true)
|
|
182
244
|
*/
|
|
183
245
|
export async function requestScreenCaptureAccess(openPreferences = true): Promise<boolean> {
|
|
184
|
-
|
|
246
|
+
const macPermissions = getMacPermissionsModule();
|
|
247
|
+
if (!macPermissions) {
|
|
185
248
|
logger.info('[Screen] Not macOS, returning true');
|
|
186
249
|
return true;
|
|
187
250
|
}
|
|
@@ -196,7 +259,7 @@ export async function requestScreenCaptureAccess(openPreferences = true): Promis
|
|
|
196
259
|
|
|
197
260
|
// Request screen capture access - this will prompt the user or open settings
|
|
198
261
|
logger.info('[Screen] Requesting screen capture access...');
|
|
199
|
-
askForScreenCaptureAccess(openPreferences);
|
|
262
|
+
macPermissions.askForScreenCaptureAccess(openPreferences);
|
|
200
263
|
|
|
201
264
|
// Check the status after requesting
|
|
202
265
|
const newStatus = getPermissionStatus('screen');
|
|
@@ -218,13 +281,14 @@ export function getFullDiskAccessStatus(): PermissionStatus {
|
|
|
218
281
|
* user must manually add the app in System Settings
|
|
219
282
|
*/
|
|
220
283
|
export function requestFullDiskAccess(): void {
|
|
221
|
-
|
|
284
|
+
const macPermissions = getMacPermissionsModule();
|
|
285
|
+
if (!macPermissions) {
|
|
222
286
|
logger.info('[FullDiskAccess] Not macOS, skipping');
|
|
223
287
|
return;
|
|
224
288
|
}
|
|
225
289
|
|
|
226
290
|
logger.info('[FullDiskAccess] Opening Full Disk Access settings...');
|
|
227
|
-
askForFullDiskAccess();
|
|
291
|
+
macPermissions.askForFullDiskAccess();
|
|
228
292
|
}
|
|
229
293
|
|
|
230
294
|
/**
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"fixes": [
|
|
5
|
+
"Fix duplicate agent and group, Fix Windows desktop build error with macOS native module, force plain text paste in ChatInput editor."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2026-01-11",
|
|
9
|
+
"version": "2.0.0-next.265"
|
|
10
|
+
},
|
|
2
11
|
{
|
|
3
12
|
"children": {
|
|
4
13
|
"fixes": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.265",
|
|
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",
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
|
203
203
|
"@lobehub/chat-plugins-gateway": "^1.9.0",
|
|
204
204
|
"@lobehub/desktop-ipc-typings": "workspace:*",
|
|
205
|
-
"@lobehub/editor": "^3.
|
|
205
|
+
"@lobehub/editor": "^3.11.0",
|
|
206
206
|
"@lobehub/icons": "^4.0.2",
|
|
207
207
|
"@lobehub/market-sdk": "0.28.1",
|
|
208
208
|
"@lobehub/tts": "^4.0.2",
|
|
@@ -3,7 +3,9 @@ import { INBOX_SESSION_ID } from '@lobechat/const';
|
|
|
3
3
|
import { eq } from 'drizzle-orm';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
5
|
|
|
6
|
+
import { getTestDB } from '../../core/getTestDB';
|
|
6
7
|
import {
|
|
8
|
+
NewAgent,
|
|
7
9
|
agents,
|
|
8
10
|
agentsFiles,
|
|
9
11
|
agentsKnowledgeBases,
|
|
@@ -11,12 +13,12 @@ import {
|
|
|
11
13
|
documents,
|
|
12
14
|
files,
|
|
13
15
|
knowledgeBases,
|
|
16
|
+
sessionGroups,
|
|
14
17
|
sessions,
|
|
15
18
|
users,
|
|
16
19
|
} from '../../schemas';
|
|
17
20
|
import { LobeChatDatabase } from '../../type';
|
|
18
21
|
import { AgentModel } from '../agent';
|
|
19
|
-
import { getTestDB } from '../../core/getTestDB';
|
|
20
22
|
|
|
21
23
|
const serverDB: LobeChatDatabase = await getTestDB();
|
|
22
24
|
|
|
@@ -290,9 +292,7 @@ describe('AgentModel', () => {
|
|
|
290
292
|
// Create agent and session for user2
|
|
291
293
|
await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
|
|
292
294
|
await serverDB.insert(sessions).values({ id: sessionId, userId: userId2 });
|
|
293
|
-
await serverDB
|
|
294
|
-
.insert(agentsToSessions)
|
|
295
|
-
.values({ agentId, sessionId, userId: userId2 });
|
|
295
|
+
await serverDB.insert(agentsToSessions).values({ agentId, sessionId, userId: userId2 });
|
|
296
296
|
|
|
297
297
|
// Try to access with user1's model
|
|
298
298
|
const result = await agentModel.findBySessionId(sessionId);
|
|
@@ -1390,6 +1390,167 @@ describe('AgentModel', () => {
|
|
|
1390
1390
|
});
|
|
1391
1391
|
});
|
|
1392
1392
|
|
|
1393
|
+
describe('duplicate', () => {
|
|
1394
|
+
it('should duplicate an agent with all config fields', async () => {
|
|
1395
|
+
// Create source agent with full config
|
|
1396
|
+
const [sourceAgent] = await serverDB
|
|
1397
|
+
.insert(agents)
|
|
1398
|
+
.values({
|
|
1399
|
+
userId,
|
|
1400
|
+
title: 'Original Agent',
|
|
1401
|
+
description: 'Original description',
|
|
1402
|
+
tags: ['tag1', 'tag2'],
|
|
1403
|
+
avatar: 'avatar-url',
|
|
1404
|
+
backgroundColor: '#ffffff',
|
|
1405
|
+
plugins: ['plugin1'],
|
|
1406
|
+
model: 'gpt-4',
|
|
1407
|
+
provider: 'openai',
|
|
1408
|
+
systemRole: 'You are helpful',
|
|
1409
|
+
openingMessage: 'Hello!',
|
|
1410
|
+
openingQuestions: ['Q1', 'Q2'],
|
|
1411
|
+
chatConfig: { historyCount: 10 },
|
|
1412
|
+
fewShots: [{ role: 'user', content: 'test' }],
|
|
1413
|
+
params: { temperature: 0.7 },
|
|
1414
|
+
tts: { showAllLocaleVoice: true },
|
|
1415
|
+
} as NewAgent)
|
|
1416
|
+
.returning();
|
|
1417
|
+
|
|
1418
|
+
const result = await agentModel.duplicate(sourceAgent.id);
|
|
1419
|
+
|
|
1420
|
+
expect(result).toBeDefined();
|
|
1421
|
+
expect(result?.agentId).toBeDefined();
|
|
1422
|
+
expect(result?.agentId).not.toBe(sourceAgent.id);
|
|
1423
|
+
|
|
1424
|
+
// Verify the duplicated agent
|
|
1425
|
+
const duplicatedAgent = await serverDB.query.agents.findFirst({
|
|
1426
|
+
where: eq(agents.id, result!.agentId),
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
expect(duplicatedAgent).toEqual(
|
|
1430
|
+
expect.objectContaining({
|
|
1431
|
+
// Should be copied
|
|
1432
|
+
title: 'Original Agent (Copy)',
|
|
1433
|
+
description: 'Original description',
|
|
1434
|
+
tags: ['tag1', 'tag2'],
|
|
1435
|
+
avatar: 'avatar-url',
|
|
1436
|
+
backgroundColor: '#ffffff',
|
|
1437
|
+
plugins: ['plugin1'],
|
|
1438
|
+
model: 'gpt-4',
|
|
1439
|
+
provider: 'openai',
|
|
1440
|
+
systemRole: 'You are helpful',
|
|
1441
|
+
openingMessage: 'Hello!',
|
|
1442
|
+
openingQuestions: ['Q1', 'Q2'],
|
|
1443
|
+
chatConfig: { historyCount: 10 },
|
|
1444
|
+
fewShots: [{ role: 'user', content: 'test' }],
|
|
1445
|
+
params: { temperature: 0.7 },
|
|
1446
|
+
tts: { showAllLocaleVoice: true },
|
|
1447
|
+
sessionGroupId: null,
|
|
1448
|
+
userId,
|
|
1449
|
+
// Should NOT be copied (new values)
|
|
1450
|
+
virtual: false,
|
|
1451
|
+
pinned: null,
|
|
1452
|
+
clientId: null,
|
|
1453
|
+
editorData: null,
|
|
1454
|
+
marketIdentifier: null,
|
|
1455
|
+
}),
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
// Verify these are NOT copied from source
|
|
1459
|
+
expect(duplicatedAgent?.id).not.toBe(sourceAgent.id);
|
|
1460
|
+
expect(duplicatedAgent?.slug).not.toBe(sourceAgent.slug);
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
it('should use provided title when duplicating', async () => {
|
|
1464
|
+
const [sourceAgent] = await serverDB
|
|
1465
|
+
.insert(agents)
|
|
1466
|
+
.values({ userId, title: 'Original' })
|
|
1467
|
+
.returning();
|
|
1468
|
+
|
|
1469
|
+
const result = await agentModel.duplicate(sourceAgent.id, 'Custom Title');
|
|
1470
|
+
|
|
1471
|
+
const duplicatedAgent = await serverDB.query.agents.findFirst({
|
|
1472
|
+
where: eq(agents.id, result!.agentId),
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
expect(duplicatedAgent?.title).toBe('Custom Title');
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
it('should return null for non-existent agent', async () => {
|
|
1479
|
+
const result = await agentModel.duplicate('non-existent-id');
|
|
1480
|
+
|
|
1481
|
+
expect(result).toBeNull();
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
it('should not duplicate another user agent', async () => {
|
|
1485
|
+
const [sourceAgent] = await serverDB
|
|
1486
|
+
.insert(agents)
|
|
1487
|
+
.values({ userId: userId2, title: 'User2 Agent' })
|
|
1488
|
+
.returning();
|
|
1489
|
+
|
|
1490
|
+
const result = await agentModel.duplicate(sourceAgent.id);
|
|
1491
|
+
|
|
1492
|
+
expect(result).toBeNull();
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
it('should not copy marketIdentifier, slug, or id', async () => {
|
|
1496
|
+
const [sourceAgent] = await serverDB
|
|
1497
|
+
.insert(agents)
|
|
1498
|
+
.values({
|
|
1499
|
+
userId,
|
|
1500
|
+
title: 'Original',
|
|
1501
|
+
slug: 'original-slug',
|
|
1502
|
+
marketIdentifier: 'market-123',
|
|
1503
|
+
})
|
|
1504
|
+
.returning();
|
|
1505
|
+
|
|
1506
|
+
const result = await agentModel.duplicate(sourceAgent.id);
|
|
1507
|
+
|
|
1508
|
+
const duplicatedAgent = await serverDB.query.agents.findFirst({
|
|
1509
|
+
where: eq(agents.id, result!.agentId),
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
expect(duplicatedAgent?.id).not.toBe(sourceAgent.id);
|
|
1513
|
+
expect(duplicatedAgent?.slug).not.toBe('original-slug');
|
|
1514
|
+
expect(duplicatedAgent?.marketIdentifier).toBeNull();
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
it('should preserve sessionGroupId when duplicating', async () => {
|
|
1518
|
+
// Create a session group
|
|
1519
|
+
const [sessionGroup] = await serverDB
|
|
1520
|
+
.insert(sessionGroups)
|
|
1521
|
+
.values({ userId, name: 'Test Group' })
|
|
1522
|
+
.returning();
|
|
1523
|
+
|
|
1524
|
+
const [sourceAgent] = await serverDB
|
|
1525
|
+
.insert(agents)
|
|
1526
|
+
.values({ userId, title: 'Agent in Group', sessionGroupId: sessionGroup.id })
|
|
1527
|
+
.returning();
|
|
1528
|
+
|
|
1529
|
+
const result = await agentModel.duplicate(sourceAgent.id);
|
|
1530
|
+
|
|
1531
|
+
const duplicatedAgent = await serverDB.query.agents.findFirst({
|
|
1532
|
+
where: eq(agents.id, result!.agentId),
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
expect(duplicatedAgent?.sessionGroupId).toBe(sessionGroup.id);
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
it('should handle agent with null title', async () => {
|
|
1539
|
+
const [sourceAgent] = await serverDB
|
|
1540
|
+
.insert(agents)
|
|
1541
|
+
.values({ userId, title: null })
|
|
1542
|
+
.returning();
|
|
1543
|
+
|
|
1544
|
+
const result = await agentModel.duplicate(sourceAgent.id);
|
|
1545
|
+
|
|
1546
|
+
const duplicatedAgent = await serverDB.query.agents.findFirst({
|
|
1547
|
+
where: eq(agents.id, result!.agentId),
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
expect(duplicatedAgent?.title).toBe('Copy');
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1393
1554
|
describe('queryAgents', () => {
|
|
1394
1555
|
it('should return non-virtual agents for the user', async () => {
|
|
1395
1556
|
// Create non-virtual agents
|