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
8 changes: 7 additions & 1 deletion build/plugin-recovery.html
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,13 @@ <h1 id="heading"></h1>
primary.disabled = true
primary.textContent = model.primaryBusyLabel
restart.disabled = true
navigate(model.canUninstall ? 'uninstall' : 'show-log')
if (model.canUninstall) {
navigate('uninstall')
} else if (model.canResetData) {
navigate('reset-data')
} else {
navigate('show-log')
}
})
restart.addEventListener('click', () => {
restart.disabled = true
Expand Down
44 changes: 42 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { spawn } from 'node:child_process'
import { join } from 'node:path'
import { appendFileSync, existsSync, readFileSync } from 'node:fs'
import { mkdir, rename } from 'node:fs/promises'
import { parse } from 'yaml'
import {
app,
Expand Down Expand Up @@ -51,13 +52,14 @@ import {
import { buildPluginRecoveryViewModel } from './plugin-recovery-view'
import { aboutDetail, bundledHarnessVersion } from './version-info'

type PluginRecoveryAction = 'uninstall' | 'show-log' | 'quit' | 'restart' | 'refresh'
type PluginRecoveryAction = 'uninstall' | 'show-log' | 'quit' | 'restart' | 'refresh' | 'reset-data'

const PLUGIN_RECOVERY_ACTIONS = new Set<PluginRecoveryAction>([
'uninstall',
'show-log',
'quit',
'restart'
'restart',
'reset-data'
])

let mainWindow: BrowserWindow | undefined
Expand Down Expand Up @@ -637,6 +639,28 @@ function showUnexpectedError(error: unknown): void {
dialog.showErrorBox('DSH Desktop encountered an error', message)
}

async function resetHarnessData(): Promise<boolean> {
const harnessHome = join(app.getPath('userData'), 'harness')
if (!existsSync(harnessHome)) {
await mkdir(harnessHome, { recursive: true })
return true
}

const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const backupPath = join(app.getPath('userData'), `harness-backup-${timestamp}`)

try {
await rename(harnessHome, backupPath)
await mkdir(harnessHome, { recursive: true })
return true
} catch (error) {
console.warn(
`[plugin-recovery] Failed to reset Harness data: ${error instanceof Error ? error.message : String(error)}`
)
return false
}
}

async function showPluginRecovery(options?: {
message?: string
logs?: readonly string[]
Expand Down Expand Up @@ -749,6 +773,22 @@ async function showPluginRecovery(options?: {
return
}
continue
} else if (action === 'reset-data') {
const resetOk = await resetHarnessData()
if (!resetOk) {
notice = isChinese
? '无法备份或重置 Harness 数据。请打开 Harness 日志查看详情,或手动重试。'
: 'The Harness data could not be backed up or reset. Open the Harness log for details or try again manually.'
continue
}
pluginRecoveryRemovedPlugins.length = 0
await launchHarness()
if (applyPendingFrontendEvidence()) continue
if (runtime.snapshot().phase === 'ready') {
schedulePluginRecoverySessionReset()
return
}
continue
} else if (action === 'show-log') {
shell.showItemInFolder(join(app.getPath('logs'), 'harness.log'))
continue
Expand Down
31 changes: 23 additions & 8 deletions src/main/plugin-recovery-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export interface PluginRecoveryViewModel {
rawError: string
quitLabel: string
canUninstall: boolean
canResetData: boolean
resetLabel: string
resetBusyLabel: string
resetDetail: string
resetSafetyNote: string
}

interface FailureDescription {
Expand Down Expand Up @@ -167,7 +172,7 @@ export function buildPluginRecoveryViewModel(options: {
: 'Harness 暂时无法启动',
summary: canUninstall
? ''
: '目前还无法定位到具体插件。请打开 Harness 日志查看详细错误。',
: '目前还无法定位到具体插件。可以重置 Harness 数据(自动备份后重建)后再次启动,或打开日志查看详细错误。',
reasonTitle: description.title,
reasonDetail: description.detail,
plugins,
Expand All @@ -179,8 +184,8 @@ export function buildPluginRecoveryViewModel(options: {
safetyNote: '工作区、会话、模型配置和其他插件不会被删除。',
primaryLabel: canUninstall
? multiple ? `卸载这 ${plugins.length} 个插件并继续检测` : '卸载此插件并继续检测'
: '打开 Harness 日志',
primaryBusyLabel: canUninstall ? '正在处理并重新检测…' : '正在打开日志…',
: '重置 Harness 数据',
primaryBusyLabel: canUninstall ? '正在处理并重新检测…' : '正在备份并重置…',
restartLabel: '重启 Harness',
restartBusyLabel: '正在重启…',
logLabel: '打开 Harness 日志',
Expand All @@ -190,7 +195,12 @@ export function buildPluginRecoveryViewModel(options: {
launchDirectory: snapshot.launchDirectory,
rawError: snapshot.message,
quitLabel: '退出 DSH Desktop',
canUninstall
canUninstall,
canResetData: !canUninstall,
resetLabel: '重置 Harness 数据',
resetBusyLabel: '正在备份并重置…',
resetDetail: '备份当前的 Harness 数据后重新创建一个干净的工作目录,再自动尝试启动。',
resetSafetyNote: '会先备份当前数据再清理;工作区和模型配置会被保留。'
}
}

Expand All @@ -203,7 +213,7 @@ export function buildPluginRecoveryViewModel(options: {
: 'Harness could not start',
summary: canUninstall
? ''
: 'No specific plugin could be identified. Open the Harness log to inspect the detailed error.',
: 'No specific plugin could be identified. Reset Harness data to back up the current directory and recreate a clean working folder, or open the log to inspect the detailed error.',
reasonTitle: description.title,
reasonDetail: description.detail,
plugins,
Expand All @@ -215,8 +225,8 @@ export function buildPluginRecoveryViewModel(options: {
safetyNote: 'Your workspaces, sessions, model settings, and other plugins will not be removed.',
primaryLabel: canUninstall
? multiple ? `Remove these ${plugins.length} plugins and continue` : 'Remove this plugin and continue'
: 'Open Harness log',
primaryBusyLabel: canUninstall ? 'Removing and checking again…' : 'Opening log…',
: 'Reset Harness data',
primaryBusyLabel: canUninstall ? 'Removing and checking again…' : 'Backing up and resetting…',
restartLabel: 'Restart Harness',
restartBusyLabel: 'Restarting…',
logLabel: 'Open Harness log',
Expand All @@ -226,6 +236,11 @@ export function buildPluginRecoveryViewModel(options: {
launchDirectory: snapshot.launchDirectory,
rawError: snapshot.message,
quitLabel: 'Quit DSH Desktop',
canUninstall
canUninstall,
canResetData: !canUninstall,
resetLabel: 'Reset Harness data',
resetBusyLabel: 'Backing up and resetting…',
resetDetail: 'Back up the current Harness data and recreate a clean working folder, then retry startup automatically.',
resetSafetyNote: 'Backs up existing data first; your workspaces and model settings will be preserved.'
}
}
22 changes: 20 additions & 2 deletions test/plugin-recovery-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,34 @@ describe('plugin recovery view model', () => {
expect(model.canUninstall).toBe(true)
})

it('falls back to the log when no plugin can be identified', () => {
it('falls back to resetting harness data when no plugin can be identified', () => {
const model = buildPluginRecoveryViewModel({
snapshot: failedSnapshot(),
plugins: [],
removedPlugins: [],
locale: 'en'
})
expect(model.canUninstall).toBe(false)
expect(model.primaryLabel).toBe('Open Harness log')
expect(model.canResetData).toBe(true)
expect(model.primaryLabel).toBe('Reset Harness data')
expect(model.primaryBusyLabel).toBe('Backing up and resetting…')
expect(model.resetLabel).toBe('Reset Harness data')
expect(model.resetDetail).toContain('Back up')
expect(model.resetSafetyNote).toContain('Backs up')
expect(model.logLabel).toBe('Open Harness log')
expect(model.restartLabel).toBe('Restart Harness')
expect(model.restartBusyLabel).toBe('Restarting…')
})

it('hides the reset option when a removable plugin is identified', () => {
const model = buildPluginRecoveryViewModel({
snapshot: failedSnapshot(),
plugins: ['some-bad-plugin'],
removedPlugins: [],
locale: 'en'
})
expect(model.canUninstall).toBe(true)
expect(model.canResetData).toBe(false)
expect(model.primaryLabel).toBe('Remove this plugin and continue')
})
})
Loading