Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 7 additions & 6 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。

## 项目结构

Expand Down
15 changes: 10 additions & 5 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -913,7 +918,7 @@ async function bootstrap(): Promise<void> {
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) => {
Expand Down Expand Up @@ -989,7 +994,7 @@ async function bootstrap(): Promise<void> {
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' }
Expand Down
17 changes: 17 additions & 0 deletions src/main/state/harness-home.ts
Original file line number Diff line number Diff line change
@@ -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')
}
16 changes: 16 additions & 0 deletions test/harness-home.test.ts
Original file line number Diff line number Diff line change
@@ -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'
)
})
})
Loading