From d8648237ef93588e6beac5a13c30e10439644eb1 Mon Sep 17 00:00:00 2001 From: yaojin Date: Tue, 18 Aug 2026 17:37:20 +0800 Subject: [PATCH] fix: add Reset Harness Data recovery option for startup failures When the Harness process fails to start due to corrupted state, users currently have to manually delete the Application Support folder to recover. This change adds a 'Reset Data' button to the startup failure dialog that: - Backs up the current harness directory with a timestamp - Creates a fresh harness directory - Automatically retries startup This gives users a discoverable way to recover from corrupted state without needing to know where the application data is stored on disk. Closes #68 --- src/main/index.ts | 56 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index d118507e..808bbd3b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,6 +1,7 @@ import { spawn } from 'node:child_process' import { join } from 'node:path' -import { readFileSync } from 'node:fs' +import { existsSync, readFileSync } from "node:fs" +import { rename, stat, mkdir } from "node:fs/promises" import { parse } from 'yaml' import { app, @@ -260,6 +261,22 @@ function registerHarnessHandlers(): void { }) } +async function resetHarnessData(): Promise { + const harnessHome = join(app.getPath('userData'), 'harness') + if (!existsSync(harnessHome)) 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 { + return false + } +} + function showUnexpectedError(error: unknown): void { const message = error instanceof Error ? error.stack ?? error.message : String(error) dialog.showErrorBox('DSH Desktop encountered an error', message) @@ -271,16 +288,23 @@ async function showRuntimeFailure(snapshot: RuntimeSnapshot): Promise { try { while (!quitting && runtime.snapshot().phase === 'failed') { + const isChinese = harnessLocale() === 'zh' const options: MessageBoxOptions = { type: 'error', - title: 'Harness could not start', + title: isChinese ? 'Harness 无法启动' : 'Harness could not start', message: snapshot.message, detail: snapshot.launchDirectory - ? `Launch directory: ${snapshot.launchDirectory}\n\nYou can retry or inspect the Harness log.` - : 'You can retry or inspect the Harness log.', - buttons: ['Retry', 'Show Log', 'Quit'], + ? isChinese + ? `启动目录: ${snapshot.launchDirectory}\n\n您可以重试、查看日志,或重置 Harness 数据(将备份当前数据)。` + : `Launch directory: ${snapshot.launchDirectory}\n\nYou can retry, inspect the log, or reset Harness data (your current data will be backed up).` + : isChinese + ? '您可以重试、查看日志,或重置 Harness 数据(将备份当前数据)。' + : 'You can retry, inspect the log, or reset Harness data (your current data will be backed up).', + buttons: isChinese + ? ['重试', '查看日志', '重置数据', '退出'] + : ['Retry', 'Show Log', 'Reset Data', 'Quit'], defaultId: 0, - cancelId: 2, + cancelId: 3, noLink: true } const result = mainWindow @@ -292,6 +316,26 @@ async function showRuntimeFailure(snapshot: RuntimeSnapshot): Promise { } else if (result.response === 1) { shell.showItemInFolder(join(app.getPath('logs'), 'harness.log')) continue + } else if (result.response === 2) { + const resetOk = await resetHarnessData() + if (resetOk) { + await launchHarness() + } else { + const fallbackOptions: MessageBoxOptions = { + type: 'warning', + title: isChinese ? '重置失败' : 'Reset Failed', + message: isChinese ? '无法重置 Harness 数据。' : 'Could not reset Harness data.', + detail: isChinese + ? '请手动删除 Harness 目录后重试。' + : 'Try deleting the Harness directory manually, then retry.', + buttons: ['OK'], + noLink: true + } + mainWindow + ? await dialog.showMessageBox(mainWindow, fallbackOptions) + : await dialog.showMessageBox(fallbackOptions) + continue + } } else { app.quit() }