fix(desktop): preserve recovery failure while report is unavailable
This commit is contained in:
1 parent
37d26dd098
commit
dd746aa580
5 files changed
+35
-12
No files matched your search
@@ -15,8 +15,8 @@
|
||||
en: 1e7d530e4e4e23d2
|
||||
zh: 859ce2e5f1d94937
|
||||
/desktop-recovery-and-diagnostics/report-contents:
|
||||
en: b4e60d5a5a0e9072
|
||||
zh: 357ff09bc9e0e924
|
||||
en: 50d8a3e5e285dd75
|
||||
zh: ed287e12a8f7557f
|
||||
/desktop-recovery-and-diagnostics/recent-operation-records:
|
||||
en: 6be041c1187e78de
|
||||
zh: 52ef5c179a4bb337
|
||||
|
||||
@@ -27,7 +27,7 @@ Recovery waits for Host shutdown before changing plugin activation. The native r
|
||||
|
||||
## Report contents
|
||||
|
||||
Native dialog details include at most 1,200 UTF-16 code units and eight diagnostic lines, plus the path of the crash report described below when one was written. Host error diagnostics retain only the last 64 Ki characters written to stderr. Earlier output is discarded so a long-running Host does not grow the shell’s diagnostic buffer indefinitely.
|
||||
Native dialog details include at most 1,200 UTF-16 code units and eight diagnostic lines, plus the crash report path or its pending/unavailable status. Retrieving a pending or unavailable report preserves any recovery-operation failure headline and details. Host error diagnostics retain only the last 64 Ki characters written to stderr. Earlier output is discarded so a long-running Host does not grow the shell’s diagnostic buffer indefinitely.
|
||||
|
||||
Before the first fatal dialog opens, Electron writes a crash report to the platform logs directory (`app.getPath('logs')`: `~/Library/Logs/DeepSeek Harness` on macOS, `logs` under the application’s `userData` directory on Windows and Linux), waiting at most one second for the write; a slow or failed write shows the dialog with an explicit pending or unavailable status. Get diagnostic report checks the same write and reveals a saved file in the system file manager without restarting or stopping the Host; a write that completes after the initial deadline remains available. The user attaches that file to a support ticket. If writing fails, the dialog hides Get diagnostic report and suggests providing a screenshot.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Host 设置可用前,恢复对话框使用本地缓存中最后确认的 Deskt
|
||||
|
||||
## 报告包含什么
|
||||
|
||||
原生弹窗详情最多包含 1,200 个 UTF-16 代码单元和八行诊断,若已写入下述崩溃报告则附上其路径。Host 错误诊断仅保留 stderr 输出的最后 64 Ki 个字符。更早的输出会被丢弃,避免长期运行的 Host 使壳的诊断缓冲区无限增长。
|
||||
原生弹窗详情最多包含 1,200 个 UTF-16 代码单元和八行诊断,并附上崩溃报告路径或仍在保存、无法保存的状态。获取仍在保存或无法保存的报告时,保留恢复操作失败的标题和详情。Host 错误诊断仅保留 stderr 输出的最后 64 Ki 个字符。更早的输出会被丢弃,避免长期运行的 Host 使壳的诊断缓冲区无限增长。
|
||||
|
||||
首个致命弹窗打开前,Electron 会向平台日志目录(`app.getPath('logs')`:macOS 为 `~/Library/Logs/DeepSeek Harness`,Windows 与 Linux 为应用 `userData` 目录下的 `logs`)写入一份崩溃报告,最多等待写入一秒;写入缓慢或失败时明确显示仍在保存或无法保存。“获取诊断报告”检查同一次写入,并在系统文件管理器中定位已保存的文件,不停止 Host 或重启;超过首次等待期限后写入完成的文件仍可获取。用户可将文件作为工单附件;写入失败时隐藏获取报告按钮,并提示提供窗口截图。
|
||||
|
||||
|
||||
@@ -29,12 +29,7 @@ const DETAIL_BUDGET = 1200
|
||||
/** Tail lines of the error kept in the dialog; the report file holds the rest. */
|
||||
const DETAIL_TAIL_LINES = 8
|
||||
|
||||
/**
|
||||
* Compose the dialog detail: the error's last lines (prefixed by the
|
||||
* shortening notice when they are not the whole error), then the report path
|
||||
* when one was written, then the reinstall advice. The report line does not
|
||||
* depend on shortening: a short error that was persisted names its file too.
|
||||
*/
|
||||
/** Error tail with a shortening notice when needed, report path or write status, and reinstall advice. */
|
||||
function dialogDetail(error: string, messages: DesktopMessages, reportState: ReportState): string {
|
||||
const advice = `\n\n${messages.startupReinstallAdvice}`
|
||||
const report = reportLine(messages, reportState)
|
||||
@@ -106,8 +101,6 @@ export class DesktopFatalRecovery {
|
||||
console.error('dsh desktop: could not reveal crash report', failure)
|
||||
message = messages.reportRevealFailed
|
||||
}
|
||||
} else {
|
||||
message = messages.fatalSummary
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -300,3 +300,33 @@ it('does not attempt to reveal an unavailable report', async () => {
|
||||
expect(operations.show.mock.calls[0]![0].buttons).not.toContain(operations.messages().getDiagnosticReport)
|
||||
expect(operations.show.mock.calls[0]![0].detail).toContain(operations.messages().reportUnavailable)
|
||||
})
|
||||
|
||||
it.each(['pending', 'failed'] as const)('keeps the recovery failure visible when fetching a %s report', async (state) => {
|
||||
vi.useFakeTimers()
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const write = Promise.withResolvers<string | undefined>()
|
||||
try {
|
||||
const { operations, stopped, recovery } = fixture('en', () => write.promise)
|
||||
stopped.resolve(undefined)
|
||||
operations.disablePlugins.mockRejectedValueOnce(new Error('profile is read-only'))
|
||||
operations.show.mockResolvedValueOnce({ response: 2, checkboxChecked: false })
|
||||
.mockImplementationOnce(async () => {
|
||||
if (state === 'failed') write.resolve(undefined)
|
||||
return { response: 3, checkboxChecked: false }
|
||||
})
|
||||
.mockResolvedValueOnce({ response: 0, checkboxChecked: false })
|
||||
const pending = recovery.report(new Error('original startup failure'), 'host')
|
||||
await vi.runAllTimersAsync()
|
||||
await pending
|
||||
expect(operations.show).toHaveBeenCalledTimes(3)
|
||||
const last = operations.show.mock.calls[2]![0]
|
||||
expect(last.message).toBe(operations.messages().recoveryOperationFailed)
|
||||
expect(last.detail).toContain('profile is read-only')
|
||||
expect(last.detail).toContain(state === 'pending' ? operations.messages().reportPending : operations.messages().reportUnavailable)
|
||||
expect(operations.revealReport).not.toHaveBeenCalled()
|
||||
expect(operations.writeReport).toHaveBeenCalledOnce()
|
||||
} finally {
|
||||
write.resolve(undefined)
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user