diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index dacd3424..168f515a 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -27,7 +27,7 @@ import { fmtAge, fmtTokens } from "./lib/format.js"; import { resolveDashboardBase } from "./lib/dashboardBase.js"; import { submitPauseResume } from "./lib/pauseAction.js"; import { postModel, postEffort } from "./lib/modelEffort.js"; -import { postCancelTurn } from "./lib/termActions.js"; +import { postCancelTurn, postLogout } from "./lib/termActions.js"; import type { TokenUsage } from "./types.js"; type OpenPanel = "inbox" | "todos" | null; @@ -225,6 +225,7 @@ export function Root() { onTogglePause={() => {}} thinking={false} onCancelTurn={() => Promise.resolve()} + onLogout={() => Promise.resolve()} /> )} @@ -331,6 +332,15 @@ export function Root() { refresh(); }) } + onLogout={() => + postLogout().then((r) => { + if (!r.ok) + liveStreamRef.current?.pushNote( + `✗ /logout failed${r.detail ? ": " + r.detail : ""}`, + ); + refresh(); + }) + } /> )} diff --git a/frontend/packages/agent/src/components/StatusChips.tsx b/frontend/packages/agent/src/components/StatusChips.tsx index f354c9bd..8a1f1eaf 100644 --- a/frontend/packages/agent/src/components/StatusChips.tsx +++ b/frontend/packages/agent/src/components/StatusChips.tsx @@ -37,6 +37,11 @@ export interface StatusChipsProps { lastTurnLabel?: string; thinking: boolean; onCancelTurn: () => Promise; + /** Rotates OAuth creds + parks the agent in needs-login. Always + * offered (unlike cancel-turn, not gated on `thinking`) — the old + * page's overflow menu had it unconditionally too. See + * `termActions.ts`'s `postLogout` for what it actually does. */ + onLogout: () => Promise; } const MODEL_DESCRIPTIONS: Record = { @@ -114,6 +119,7 @@ function StatusMenu({ onTogglePause, thinking, onCancelTurn, + onLogout, }: Pick< StatusChipsProps, | "statusLabel" @@ -123,14 +129,17 @@ function StatusMenu({ | "onTogglePause" | "thinking" | "onCancelTurn" + | "onLogout" >) { const [open, setOpen] = useState(false); const [confirmCancel, setConfirmCancel] = useState(false); + const [confirmLogout, setConfirmLogout] = useState(false); const anchorRef = useRef(null); function close() { setOpen(false); setConfirmCancel(false); + setConfirmLogout(false); } const options: DropdownOption[] = [ @@ -143,6 +152,13 @@ function StatusMenu({ danger: true, }); } + // Always offered, regardless of turn state — same as the old overflow + // menu's unconditional 🔓 logout entry. + options.push({ + value: "logout", + label: confirmLogout ? "🔓 click again to confirm" : "🔓 logout", + danger: true, + }); return (
@@ -163,12 +179,22 @@ function StatusMenu({ close(); return; } - // value === 'cancel-turn': first click arms it, second fires. - if (!confirmCancel) { - setConfirmCancel(true); + if (value === "cancel-turn") { + // First click arms it, second fires. + if (!confirmCancel) { + setConfirmCancel(true); + return; + } + onCancelTurn(); + close(); return; } - onCancelTurn(); + // value === 'logout': same arm-then-fire shape as cancel-turn. + if (!confirmLogout) { + setConfirmLogout(true); + return; + } + onLogout(); close(); }} onClose={close} @@ -196,6 +222,7 @@ export function StatusChips({ lastTurnLabel, thinking, onCancelTurn, + onLogout, }: StatusChipsProps) { return (
@@ -207,6 +234,7 @@ export function StatusChips({ onTogglePause={onTogglePause} thinking={thinking} onCancelTurn={onCancelTurn} + onLogout={onLogout} />