cc-haha/src/server/middleware/errorHandler.ts
程序员阿江(Relakkes) 071975e0cc Improve desktop failure diagnosis with exportable logs
Desktop failures were previously hard to debug from issue reports because server-side and CLI startup details were only partially visible in the UI. This adds a dedicated cc-haha diagnostics store with sanitized structured events, runtime error summaries, a Settings diagnostics view, and an exportable bundle that users can attach to reports.

Constraint: Diagnostic exports must not include chat content, file contents, full environment variables, API keys, bearer tokens, cookies, or OAuth tokens.
Rejected: Export raw server logs | easier to debug but too likely to leak secrets and private workspace data.
Rejected: Keep diagnostics only in transient UI errors | still leaves maintainers unable to diagnose later GitHub issues.
Confidence: high
Scope-risk: moderate
Directive: Do not add raw transcript, prompt, attachment, or environment dumps to diagnostics without a separate privacy review.
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/diagnostics-service.test.ts
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/conversation-service.test.ts
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run test src/__tests__/diagnosticsSettings.test.tsx --run
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser E2E on local server/dev UI for CLI startup failure, provider test failure, diagnostics tab, copy summary, export bundle, and tar/secret scan
Not-tested: Destructive clear-logs button in browser E2E; local deletion was intentionally not clicked.
2026-05-02 12:41:31 +08:00

54 lines
1.3 KiB
TypeScript

/**
* Unified error handling utilities
*/
import { diagnosticsService } from '../services/diagnosticsService.js'
export class ApiError extends Error {
constructor(
public statusCode: number,
message: string,
public code?: string
) {
super(message)
this.name = 'ApiError'
}
static badRequest(message: string) {
return new ApiError(400, message, 'BAD_REQUEST')
}
static notFound(message: string) {
return new ApiError(404, message, 'NOT_FOUND')
}
static conflict(message: string) {
return new ApiError(409, message, 'CONFLICT')
}
static internal(message: string) {
return new ApiError(500, message, 'INTERNAL_ERROR')
}
}
export function errorResponse(error: unknown): Response {
if (error instanceof ApiError) {
return Response.json(
{ error: error.code || 'ERROR', message: error.message },
{ status: error.statusCode }
)
}
void diagnosticsService.recordEvent({
type: 'api_unhandled_error',
severity: 'error',
summary: error instanceof Error ? error.message : String(error),
details: error,
})
console.error('[Server] Unexpected error:', error)
return Response.json(
{ error: 'INTERNAL_ERROR', message: 'An unexpected error occurred' },
{ status: 500 }
)
}