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
+15
View File
@@ -13,6 +13,8 @@ import {
} from '@/sync/session-directory-resolution';
import { useSessionWorktreeStore } from '@/sync/session-worktree-store';
import { getRecentSendFailures } from '@/sync/send-failure-log';
import { getRecentSessionErrors } from '@/sync/session-error-log';
import { buildOpenCodeStatusReport } from '@/lib/openCodeStatus';
import { getAttachedSessionDirectory } from '@/sync/session-worktree-contract';
import { useStreamingStore } from '@/sync/streaming';
import { runtimeFetch } from '@/lib/runtime-fetch';
@@ -386,6 +388,9 @@ export const debugUtils = {
// this session, so a "my message disappeared" report is not a rejected
// send and needs a different explanation.
recentSendFailures: getRecentSendFailures(),
// Same reasoning: empty means OpenCode reported no failed turn in this
// app session.
recentSessionErrors: getRecentSessionErrors(),
currentSessionDirectoryResolution: sessionState.currentSessionId
? this.diagnoseSessionDirectory(sessionState.currentSessionId)
: null,
@@ -395,6 +400,16 @@ export const debugUtils = {
return report;
},
/**
* The same text the status report dialog (Ctrl/Cmd+Shift+L) shows, for a
* console or remote session that cannot press the shortcut.
*/
async statusReport() {
const text = await buildOpenCodeStatusReport();
console.log(text);
return text;
},
/**
* Prompt sends that were rejected and rolled back in this app session.
* Newest first; empty means no send was rejected.