fix(web): confirm sidebar Update Hermes before running

Match the Restart Gateway flow with a confirm dialog that fetches cached
update metadata so users see commit-behind context before applying.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Austin Pickett 2026-06-29 09:17:10 -04:00 committed by Teknium
parent dbe92b9ed1
commit 75d4aa9325
3 changed files with 72 additions and 1 deletions

View file

@ -100,7 +100,7 @@ import type { PluginManifest } from "@/plugins";
import { useTheme } from "@/themes";
import { isDashboardEmbeddedChatEnabled } from "@/lib/dashboard-flags";
import { api } from "@/lib/api";
import type { StatusResponse } from "@/lib/api";
import type { StatusResponse, UpdateCheckResponse } from "@/lib/api";
function RootRedirect() {
return <Navigate to="/sessions" replace />;
@ -903,6 +903,46 @@ function SidebarSystemActions({
useSystemActions();
const canUpdateHermes = status?.can_update_hermes === true;
const [restartConfirmOpen, setRestartConfirmOpen] = useState(false);
const [updateConfirmOpen, setUpdateConfirmOpen] = useState(false);
const [updateConfirmInfo, setUpdateConfirmInfo] =
useState<UpdateCheckResponse | null>(null);
const [updateConfirmChecking, setUpdateConfirmChecking] = useState(false);
useEffect(() => {
if (!updateConfirmOpen) {
setUpdateConfirmInfo(null);
return;
}
let cancelled = false;
setUpdateConfirmChecking(true);
api
.checkHermesUpdate(false)
.then((info) => {
if (!cancelled) setUpdateConfirmInfo(info);
})
.catch(() => {
if (!cancelled) setUpdateConfirmInfo(null);
})
.finally(() => {
if (!cancelled) setUpdateConfirmChecking(false);
});
return () => {
cancelled = true;
};
}, [updateConfirmOpen]);
const updateConfirmDescription = useMemo(() => {
if (updateConfirmInfo?.behind && updateConfirmInfo.behind > 0) {
const cmd = updateConfirmInfo.update_command;
const n = updateConfirmInfo.behind;
return `This will run 'hermes update' (${cmd}) and pull ${n} new commit${n === 1 ? "" : "s"}. The gateway restarts when the update finishes; the current session keeps its prompt cache until then.`;
}
const cmd = updateConfirmInfo?.update_command ?? "hermes update";
return (
t.status.updateHermesConfirmMessage ??
`This will run 'hermes update' (${cmd}) and restart the gateway when it finishes.`
);
}, [t.status.updateHermesConfirmMessage, updateConfirmInfo]);
const items: SystemActionItem[] = [
{
@ -929,6 +969,10 @@ function SidebarSystemActions({
setRestartConfirmOpen(true);
return;
}
if (action === "update") {
setUpdateConfirmOpen(true);
return;
}
void runAction(action);
navigate("/sessions");
onNavigate();
@ -941,6 +985,13 @@ function SidebarSystemActions({
onNavigate();
};
const confirmUpdate = () => {
setUpdateConfirmOpen(false);
void runAction("update");
navigate("/sessions");
onNavigate();
};
return (
<>
<div
@ -997,6 +1048,19 @@ function SidebarSystemActions({
t.status.restartGatewayConfirmTitle ?? `${t.status.restartGateway}?`
}
/>
<ConfirmDialog
cancelLabel={t.common.cancel}
confirmLabel={t.status.updateHermesConfirmNow ?? "Update now"}
description={
updateConfirmChecking ? t.common.loading : updateConfirmDescription
}
loading={pendingAction === "update" || updateConfirmChecking}
onCancel={() => setUpdateConfirmOpen(false)}
onConfirm={confirmUpdate}
open={updateConfirmOpen}
title={t.status.updateHermesConfirmTitle ?? `${t.status.updateHermes}?`}
/>
</>
);
}

View file

@ -132,6 +132,10 @@ export const en: Translations = {
startedInBackground: "Started in background — check logs for progress",
stopped: "Stopped",
updateHermes: "Update Hermes",
updateHermesConfirmMessage:
"This runs hermes update and restarts the gateway when it finishes. Active sessions keep their prompt cache until then.",
updateHermesConfirmNow: "Update now",
updateHermesConfirmTitle: "Update Hermes?",
updatingHermes: "Updating Hermes…",
waitingForOutput: "Waiting for output…",
},

View file

@ -149,6 +149,9 @@ export interface Translations {
startedInBackground: string;
stopped: string;
updateHermes: string;
updateHermesConfirmMessage?: string;
updateHermesConfirmNow?: string;
updateHermesConfirmTitle?: string;
updatingHermes: string;
waitingForOutput: string;
};