Files
Bohdan Triapitsyn b8465ae133 fix: harden and de-slop the merged contribution batch
Follow-ups promised on merge, plus review findings on the batch itself:

- chat: task-tool output now respects the 512KiB render cap; quick-open
  icon is visible at rest on coarse pointers and reachable by keyboard
  (row keydown no longer swallows inner-button Enter/Space); composer
  inline-code decoration drops the metric-shifting padding; a btw fork
  send carries only the boundary instruction, never the promotion notice
- sync: cascade revert/unrevert aborts busy descendants, busy state is
  read from every child store at the moment of use; rule 9 documents
  redo clearing all descendant revert markers
- electron: renderer recovery keeps memory-eviction (a valid
  render-process-gone reason) and both windows share one
  attachRendererRecovery helper
- vscode: process registry is a thin re-export of the web module
  (provider-env-aliases precedent) with ordered register/unregister
  writes and an awaited close
- server/cli: managed-process registry takes injectable deps (fixes the
  unreaped-orphans ReferenceError), corrupt settings errors name the
  file, getWorktrees test restores console.warn
- tests: module-mock harnesses removed (AgentsSidebar, SettingsView
  mobile focus — behaviors stay live but uncovered, accepted trade),
  QuestionMarkdown asserts rendered DOM
- i18n: German gains the debug-panel request keys, Japanese/German drop
  removed worktree keys, Ukrainian unit spacing fixed
- changelog: Copilot AI Credits entries (main + VS Code)
2026-08-28 02:08:09 +03:00

55 lines
1.4 KiB
JavaScript

const RECOVERY_WINDOW_MS = 60_000;
const MAX_RECOVERY_ATTEMPTS = 3;
const RECOVERABLE_REASONS = new Set([
'abnormal-exit',
'crashed',
'oom',
'memory-eviction',
]);
const RELOAD_DELAY_MS = 100;
export const createRendererRecoveryPolicy = (now = Date.now) => {
let windowStartedAt = 0;
let attempts = 0;
return {
shouldReload: (reason) => {
if (!RECOVERABLE_REASONS.has(reason)) return false;
const currentTime = now();
if (currentTime - windowStartedAt >= RECOVERY_WINDOW_MS) {
windowStartedAt = currentTime;
attempts = 0;
}
if (attempts >= MAX_RECOVERY_ATTEMPTS) return false;
attempts += 1;
return true;
},
};
};
/**
* Reload a window whose renderer process died, within the recovery budget.
* Shared by every BrowserWindow so the desktop shell has one recovery policy.
*/
export const attachRendererRecovery = (browserWindow, { log, label }) => {
const policy = createRendererRecoveryPolicy();
browserWindow.webContents.on('render-process-gone', (_event, details) => {
if (!policy.shouldReload(details.reason)) return;
log.warn('[electron] renderer exited unexpectedly; reloading window', {
label: browserWindow.__ocLabel,
surface: label,
reason: details.reason,
exitCode: details.exitCode,
});
setTimeout(() => {
if (!browserWindow.isDestroyed()) {
browserWindow.webContents.reload();
}
}, RELOAD_DELAY_MS);
});
};