From c534ed6dc16f131ade647b5e7089bb6c3d8cb29c Mon Sep 17 00:00:00 2001 From: yaojin Date: Thu, 20 Aug 2026 02:05:43 -0700 Subject: [PATCH] fix: share Harness data with desktop --- README.md | 13 +++++++------ README.zh.md | 13 +++++++------ src/main/index.ts | 15 ++++++++++----- src/main/state/harness-home.ts | 17 +++++++++++++++++ test/harness-home.test.ts | 16 ++++++++++++++++ 5 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 src/main/state/harness-home.ts create mode 100644 test/harness-home.test.ts diff --git a/README.md b/README.md index d688bf4c..87da2017 100644 --- a/README.md +++ b/README.md @@ -125,14 +125,15 @@ DSH Desktop (Electron Main) Electron userData ├── launch-root/ -├── logs/harness.log -└── harness/ - ├── profiles/ - ├── sessions/ - └── Plugins and user data +└── logs/harness.log + +~/.dsh (shared with standalone Harness) +├── profiles/ +├── sessions/ +└── Plugins and user data ``` -Harness runs in a separate Electron Node child process. The `--expose-internals` permission required by Cordis HMR is granted only to that child process and never to the web renderer. +Production builds use Harness's canonical `~/.dsh` data home, so projects and conversation history remain available when switching between standalone Harness and DSH Desktop. Development builds remain isolated under Electron `userData`. Harness runs in a separate Electron Node child process. The `--expose-internals` permission required by Cordis HMR is granted only to that child process and never to the web renderer. ## Project structure diff --git a/README.zh.md b/README.zh.md index 878d5157..d9e0c0d4 100644 --- a/README.zh.md +++ b/README.zh.md @@ -125,14 +125,15 @@ DSH Desktop (Electron Main) Electron userData ├── launch-root/ -├── logs/harness.log -└── harness/ - ├── profiles/ - ├── sessions/ - └── 插件与用户数据 +└── logs/harness.log + +~/.dsh(与独立 Harness 共享) +├── profiles/ +├── sessions/ +└── 插件与用户数据 ``` -Harness 运行在独立的 Electron Node 子进程中。Cordis HMR 所需的 `--expose-internals` 只授予该子进程,不会授予 Web Renderer。 +正式版使用 Harness 标准的 `~/.dsh` 数据目录,因此在独立 Harness 和 DSH Desktop 之间切换时,项目与历史对话会保持可见。开发版仍隔离在 Electron `userData` 下。Harness 运行在独立的 Electron Node 子进程中。Cordis HMR 所需的 `--expose-internals` 只授予该子进程,不会授予 Web Renderer。 ## 项目结构 diff --git a/src/main/index.ts b/src/main/index.ts index 1bf6f5b0..7a90640c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -22,6 +22,7 @@ import { } from './plugin-recovery-detection' import { secureWindow } from './security' import { ensureLaunchRoot } from './state/launch-root' +import { resolveDesktopHarnessHome } from './state/harness-home' import { resetPluginProfile, uninstallPluginFromProfile @@ -161,6 +162,10 @@ function isDevelopmentBuild(): boolean { const developmentBuild = isDevelopmentBuild() +function harnessHome(): string { + return resolveDesktopHarnessHome(app.getPath('userData'), developmentBuild) +} + function windowsTitleBarOverlay(isDark: boolean): Electron.TitleBarOverlayOptions { return { color: '#00000000', @@ -292,7 +297,7 @@ function dshBrandLogoPath(variant: 'light' | 'dark'): string { function harnessLocale(): 'en' | 'zh' { try { const settings = parse( - readFileSync(join(app.getPath('userData'), 'harness', 'settings.yaml'), 'utf8') + readFileSync(join(harnessHome(), 'settings.yaml'), 'utf8') ) as { locale?: { preference?: unknown } } return resolveHarnessLocale( settings.locale?.preference, @@ -310,7 +315,7 @@ function configureApplicationLocale(): void { function harnessThemePreference(): 'light' | 'dark' | 'system' { try { const settings = parse( - readFileSync(join(app.getPath('userData'), 'harness', 'settings.yaml'), 'utf8') + readFileSync(join(harnessHome(), 'settings.yaml'), 'utf8') ) as { 'ui-theme'?: { preference?: unknown } } const preference = settings['ui-theme']?.preference return preference === 'light' || preference === 'dark' || preference === 'system' @@ -625,7 +630,7 @@ async function showPluginRecovery(options?: { if (failureRecoveryVisible || quitting) return failureRecoveryVisible = true - const dshHome = join(app.getPath('userData'), 'harness') + const dshHome = harnessHome() const isChinese = harnessLocale() === 'zh' cancelPluginRecoverySessionReset() const removedPlugins = pluginRecoveryRemovedPlugins @@ -913,7 +918,7 @@ async function bootstrap(): Promise { nodeExecutablePath: bundledNodePath(), nodeEntryPath: harnessNodeEntryPath(), dshPatchPath: desktopResourcePath('dsh-desktop.patch.yml'), - dshHome: join(app.getPath('userData'), 'harness'), + dshHome: harnessHome(), logPath: join(app.getPath('logs'), 'harness.log'), launchProcess: (executablePath, args, options) => spawn(executablePath, args, options), onChanged: (snapshot) => { @@ -989,7 +994,7 @@ async function bootstrap(): Promise { if (pluginName !== undefined && typeof pluginName !== 'string') { throw new Error('The failing plugin name must be a string.') } - const dshHome = join(app.getPath('userData'), 'harness') + const dshHome = harnessHome() await resetPluginProfile(dshHome, pluginName) await launchHarness() return { ok: runtime.snapshot().phase === 'ready' } diff --git a/src/main/state/harness-home.ts b/src/main/state/harness-home.ts new file mode 100644 index 00000000..587642e6 --- /dev/null +++ b/src/main/state/harness-home.ts @@ -0,0 +1,17 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' + +/** + * Resolve the Harness data root used by the desktop shell. + * + * Production shares Harness's canonical ~/.dsh home so projects, sessions, + * credentials, and presets remain available when switching between the CLI + * and Desktop. Development builds stay isolated from real user data. + */ +export function resolveDesktopHarnessHome( + userDataPath: string, + developmentBuild: boolean, + homeDirectory = homedir() +): string { + return developmentBuild ? join(userDataPath, 'harness') : join(homeDirectory, '.dsh') +} diff --git a/test/harness-home.test.ts b/test/harness-home.test.ts new file mode 100644 index 00000000..6d59d2a5 --- /dev/null +++ b/test/harness-home.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { resolveDesktopHarnessHome } from '../src/main/state/harness-home' + +describe('Desktop Harness home', () => { + it('shares the canonical Harness home in production', () => { + expect(resolveDesktopHarnessHome('/app-data/dsh-desktop', false, '/users/alex')).toBe( + '/users/alex/.dsh' + ) + }) + + it('keeps development builds isolated under Electron userData', () => { + expect(resolveDesktopHarnessHome('/app-data/dsh-desktop-dev', true, '/users/alex')).toBe( + '/app-data/dsh-desktop-dev/harness' + ) + }) +})