hermes-agent/apps/desktop/src/components/error-boundary.tsx
brooklyn! 31c40c72c0
fix(desktop): stabilize project folder sessions (#37586)
* fix(desktop): stabilize project folder sessions

Keep desktop folder selection aligned with new sessions and scope TUI gateway cwd through session context so prompts and tools resolve against the selected workspace.

* fix(desktop): address review feedback on folder sessions

Snapshot sessions before iterating to avoid concurrent-mutation crashes,
optional-chain the revealLogs catch, and read console-message args from
the correct Electron event/messageDetails positions.

* fix(desktop): address second review pass on folder sessions

Sync the remembered workspace key with the cwd atom (clear on empty),
only load tree children for real directory nodes, and throttle renderer
auto-reloads so a deterministic startup crash can't loop forever.

* fix(desktop): inherit parent workspace for ephemeral agent tasks

Background and preview tasks use ephemeral ids absent from the session
map, so pass the parent session cwd into the session context explicitly
instead of clearing it back to the gateway launch dir. Also correct the
set_session_vars docstring about clear_session_vars semantics.

* fix(desktop): validate preview cwd before pinning session context

A non-empty but non-existent client cwd would pin an unusable override
and silently fall back to the launch dir. Validate once, reuse for both
the session context and the terminal override, and fall back to the
parent session workspace when invalid.

* fix(desktop): harden preview cwd normalization and adopt normalized cwd

Guard preview cwd normalization against malformed client paths so a bad
input can't fail the whole restart, and adopt the backend's normalized
config.get cwd in the no-active-session path so the persisted workspace
stays consistent with what the agent uses.
2026-06-02 20:23:09 +00:00

91 lines
3 KiB
TypeScript

import { Component, type ErrorInfo, type ReactNode } from 'react'
import { Button } from '@/components/ui/button'
import { AlertTriangle, RefreshCw } from '@/lib/icons'
export interface ErrorBoundaryFallbackProps {
error: Error
reset: () => void
}
interface ErrorBoundaryProps {
children: ReactNode
fallback?: (props: ErrorBoundaryFallbackProps) => ReactNode
label?: string
onError?: (error: Error, info: ErrorInfo) => void
}
interface ErrorBoundaryState {
error: Error | null
}
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { error: null }
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { error }
}
componentDidCatch(error: Error, info: ErrorInfo) {
const tag = this.props.label ? `[error-boundary:${this.props.label}]` : '[error-boundary]'
console.error(tag, error, info.componentStack)
this.props.onError?.(error, info)
}
reset = () => {
this.setState({ error: null })
}
render() {
const { error } = this.state
if (!error) {
return this.props.children
}
if (this.props.fallback) {
return this.props.fallback({ error, reset: this.reset })
}
return <RootErrorFallback error={error} reset={this.reset} />
}
}
function RootErrorFallback({ error, reset }: ErrorBoundaryFallbackProps) {
return (
<div className="fixed inset-0 z-[1500] flex items-center justify-center bg-(--ui-chat-surface-background) p-6">
<div className="w-full max-w-[40rem] overflow-hidden rounded-xl border border-(--ui-stroke-secondary) bg-(--ui-chat-bubble-background) shadow-sm">
<div className="flex items-start gap-3 border-b border-(--ui-stroke-tertiary) px-5 py-4">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-destructive/10 text-destructive">
<AlertTriangle className="size-5" />
</div>
<div>
<h2 className="text-[0.9375rem] font-semibold tracking-tight">Something broke in the interface</h2>
<p className="mt-1 text-[0.8125rem] leading-5 text-(--ui-text-tertiary)">
The view hit an unexpected error. Your chats and settings are safe - try again, or reload the window.
</p>
</div>
</div>
<div className="grid gap-4 p-5">
<div className="rounded-2xl border border-destructive/30 bg-destructive/10 px-4 py-3 font-mono text-[0.7rem] leading-4 text-destructive">
{error.message || String(error)}
</div>
<div className="flex flex-wrap gap-2">
<Button onClick={reset}>
<RefreshCw className="size-4" />
Try again
</Button>
<Button onClick={() => window.location.reload()} variant="outline">
Reload window
</Button>
<Button onClick={() => void window.hermesDesktop?.revealLogs()?.catch(() => undefined)} variant="ghost">
Open logs
</Button>
</div>
</div>
</div>
</div>
)
}