Watch
0
0
Fork
You've already forked hyperhive
0

agent ui: add a clickable new-session menu entry

Same gap #4611 fixed for logout: the Preact rewrite's StatusChips menu
never picked up new-session as a click path, only /new-session typed
twice into the terminal. postNewSession already existed in
termActions.ts. Wire it into the status menu with the same arm-then-
confirm click pattern logout/cancel-turn already use.

Closes #4612
This commit is contained in:
iris 2026-09-28 12:14:17 +02:00 • committed by mara
commit 9a444c27fa
2 changed files with 43 additions and 2 deletions

View file

@ -27,7 +27,11 @@ import { fmtAge, fmtTokens } from "./lib/format.js";
import { resolveDashboardBase } from "./lib/dashboardBase.js"; import { resolveDashboardBase } from "./lib/dashboardBase.js";
import { submitPauseResume } from "./lib/pauseAction.js"; import { submitPauseResume } from "./lib/pauseAction.js";
import { postModel, postEffort } from "./lib/modelEffort.js"; import { postModel, postEffort } from "./lib/modelEffort.js";
import { postCancelTurn, postLogout } from "./lib/termActions.js"; import {
postCancelTurn,
postLogout,
postNewSession,
} from "./lib/termActions.js";
import type { TokenUsage } from "./types.js"; import type { TokenUsage } from "./types.js";
type OpenPanel = "inbox" | "todos" | null; type OpenPanel = "inbox" | "todos" | null;
@ -225,6 +229,7 @@ export function Root() {
onTogglePause={() => {}} onTogglePause={() => {}}
thinking={false} thinking={false}
onCancelTurn={() => Promise.resolve()} onCancelTurn={() => Promise.resolve()}
onNewSession={() => Promise.resolve()}
onLogout={() => Promise.resolve()} onLogout={() => Promise.resolve()}
/> />
</Header> </Header>
@ -332,6 +337,15 @@ export function Root() {
refresh(); refresh();
}) })
} }
onNewSession={() =>
postNewSession().then((r) => {
if (!r.ok)
liveStreamRef.current?.pushNote(
`✗ /new-session failed${r.detail ? ": " + r.detail : ""}`,
);
refresh();
})
}
onLogout={() => onLogout={() =>
postLogout().then((r) => { postLogout().then((r) => {
if (!r.ok) if (!r.ok)

View file

@ -37,6 +37,12 @@ export interface StatusChipsProps {
lastTurnLabel?: string; lastTurnLabel?: string;
thinking: boolean; thinking: boolean;
onCancelTurn: () => Promise<void>; onCancelTurn: () => Promise<void>;
/** Drops all prior `--continue` context and starts fresh. Always
* offered, same as logout — the old app.js overflow menu had it as
* an unconditional click target too, this is just the click path
* catching up to what `/new-session` already does. See
* `termActions.ts`'s `postNewSession`. */
onNewSession: () => Promise<void>;
/** Rotates OAuth creds + parks the agent in needs-login. Always /** Rotates OAuth creds + parks the agent in needs-login. Always
* offered (unlike cancel-turn, not gated on `thinking`) — the old * offered (unlike cancel-turn, not gated on `thinking`) — the old
* page's overflow menu had it unconditionally too. See * page's overflow menu had it unconditionally too. See
@ -119,6 +125,7 @@ function StatusMenu({
onTogglePause, onTogglePause,
thinking, thinking,
onCancelTurn, onCancelTurn,
onNewSession,
onLogout, onLogout,
}: Pick< }: Pick<
StatusChipsProps, StatusChipsProps,
@ -129,16 +136,19 @@ function StatusMenu({
| "onTogglePause" | "onTogglePause"
| "thinking" | "thinking"
| "onCancelTurn" | "onCancelTurn"
| "onNewSession"
| "onLogout" | "onLogout"
>) { >) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [confirmCancel, setConfirmCancel] = useState(false); const [confirmCancel, setConfirmCancel] = useState(false);
const [confirmNewSession, setConfirmNewSession] = useState(false);
const [confirmLogout, setConfirmLogout] = useState(false); const [confirmLogout, setConfirmLogout] = useState(false);
const anchorRef = useRef<HTMLDivElement>(null); const anchorRef = useRef<HTMLDivElement>(null);
function close() { function close() {
setOpen(false); setOpen(false);
setConfirmCancel(false); setConfirmCancel(false);
setConfirmNewSession(false);
setConfirmLogout(false); setConfirmLogout(false);
} }
@ -153,7 +163,12 @@ function StatusMenu({
}); });
} }
// Always offered, regardless of turn state — same as the old overflow // Always offered, regardless of turn state — same as the old overflow
// menu's unconditional 🔓 logout entry. // menu's unconditional ↻ new-session and 🔓 logout entries.
options.push({
value: "new-session",
label: confirmNewSession ? "↻ click again to confirm" : "↻ new session",
danger: true,
});
options.push({ options.push({
value: "logout", value: "logout",
label: confirmLogout ? "🔓 click again to confirm" : "🔓 logout", label: confirmLogout ? "🔓 click again to confirm" : "🔓 logout",
@ -189,6 +204,16 @@ function StatusMenu({
close(); close();
return; return;
} }
if (value === "new-session") {
// Same arm-then-fire shape as cancel-turn/logout.
if (!confirmNewSession) {
setConfirmNewSession(true);
return;
}
onNewSession();
close();
return;
}
// value === 'logout': same arm-then-fire shape as cancel-turn. // value === 'logout': same arm-then-fire shape as cancel-turn.
if (!confirmLogout) { if (!confirmLogout) {
setConfirmLogout(true); setConfirmLogout(true);
@ -222,6 +247,7 @@ export function StatusChips({
lastTurnLabel, lastTurnLabel,
thinking, thinking,
onCancelTurn, onCancelTurn,
onNewSession,
onLogout, onLogout,
}: StatusChipsProps) { }: StatusChipsProps) {
return ( return (
@ -234,6 +260,7 @@ export function StatusChips({
onTogglePause={onTogglePause} onTogglePause={onTogglePause}
thinking={thinking} thinking={thinking}
onCancelTurn={onCancelTurn} onCancelTurn={onCancelTurn}
onNewSession={onNewSession}
onLogout={onLogout} onLogout={onLogout}
/> />
<Picker <Picker