feat(chat): surface failed turns and add error diagnostics to the status report

A turn that OpenCode stopped could end with nothing on screen: the
session.error event was only turned into a sidebar badge, its message was
dropped (the notification expected a different shape than OpenCode sends),
and a send that was accepted but never answered looked the same as success.

- The chat shows what OpenCode reported under the last message while that
  turn is the latest one, and names a user message an idle session has left
  unanswered for five seconds.
- The last 20 session errors are kept in memory and listed in the status
  report (Ctrl/Cmd+Shift+L, also `__opencodeDebug.statusReport()`), next to
  rejected sends, the managed OpenCode process's last error and stderr
  tail, and the OpenCode and desktop log file locations.
- The OpenCode health probe hits /global/health instead of a route that
  does not exist, and probe URLs resolve against the page for web runtimes.
This commit is contained in:
Bohdan Triapitsyn
2026-08-29 23:22:27 +03:00
parent d8e223bf49
commit b18933f19c
21 changed files with 390 additions and 7 deletions
+14 -1
View File
@@ -24,7 +24,8 @@ type TurnCompleteNotification = NotificationBase & {
type ErrorNotification = NotificationBase & {
type: "error"
error?: { message?: string; code?: string }
/** What OpenCode reported for the failed turn; both null when it gave no details. */
error?: { name: string | null; message: string | null }
}
export type Notification = TurnCompleteNotification | ErrorNotification
@@ -161,3 +162,15 @@ export function useSessionUnseenCount(sessionId: string): number {
return useNotificationStore((s) => s.index.session.unseenCount[sessionId] ?? 0)
}
/** The newest error OpenCode reported for this session, viewed or not. */
export function useLatestSessionError(sessionId: string): ErrorNotification | null {
return useNotificationStore((s) => {
if (!sessionId) return null
for (let index = s.list.length - 1; index >= 0; index -= 1) {
const notification = s.list[index]
if (notification.session === sessionId && notification.type === "error") return notification
}
return null
})
}