From 45988d4cbdd777aaaf32270a52f78c38da7d9c2b Mon Sep 17 00:00:00 2001 From: Serhii Vecherenko Date: Sat, 23 May 2026 18:34:17 -0700 Subject: [PATCH] feat(agent): improve auth and logout command support - Map Codex and Cursor "not logged in" status to "missing" to trigger sign-in. - Standardize agent logout command building across Claude, Codex, and Cursor. - Handle Claude upstream API failures that report as success-subtypes. - Introduce heuristic to detect agent authentication errors in the renderer. - Prevent focus blinks on composer when clicking the auth dock action button. - Ignore view transition InvalidStateError exceptions in global error handler. --- .../thread/ThreadAuthRequiredDock.tsx | 40 ++++++++++++++++--- .../thread/ThreadComposerSection.tsx | 14 +++++-- .../thread/threadErrorState.test.ts | 24 ++++++++++- .../components/thread/threadErrorState.ts | 24 +++++++++++ src/renderer/rendererGlobalErrors.test.ts | 16 ++++++++ src/renderer/rendererGlobalErrors.ts | 16 +++++++- src/supervisor/agents/base/index.ts | 16 ++++++++ src/supervisor/agents/claude/claude.test.ts | 12 ++++++ src/supervisor/agents/claude/index.ts | 2 + src/supervisor/agents/claude/probe.ts | 29 +++++++++++--- .../agents/claude/sdkCanonicalMapping.test.ts | 29 ++++++++++++++ .../agents/claude/sdkCanonicalMapping.ts | 28 ++++++++++++- src/supervisor/agents/claude/sdkSession.ts | 15 ++++++- src/supervisor/agents/codex/codex.test.ts | 20 ++++++++++ src/supervisor/agents/codex/detection.ts | 25 ++++++++++-- src/supervisor/agents/codex/index.ts | 2 + src/supervisor/agents/cursor/cursor.test.ts | 7 ++++ src/supervisor/agents/cursor/detection.ts | 2 +- src/supervisor/agents/cursor/index.ts | 11 +---- 19 files changed, 300 insertions(+), 32 deletions(-) diff --git a/src/renderer/components/thread/ThreadAuthRequiredDock.tsx b/src/renderer/components/thread/ThreadAuthRequiredDock.tsx index f3cb2bd3..0ac9aa5f 100644 --- a/src/renderer/components/thread/ThreadAuthRequiredDock.tsx +++ b/src/renderer/components/thread/ThreadAuthRequiredDock.tsx @@ -22,6 +22,17 @@ async function refreshAgentStatus(status: AgentStatus): Promise { }); } +/** + * Keep focus on whatever the user had focused before they mouse-clicked the + * dock's action button. Without this, clicking the button takes focus, the + * `isDisabled` swap during the in-flight action then releases focus, and + * react-aria restores focus to the composer for one frame before the dock + * finally unmounts — producing a visible border blink on the composer. + */ +function preventFocusSteal(event: React.MouseEvent): void { + event.preventDefault(); +} + export function ThreadAuthRequiredDock(props: { agentStatus: AgentStatus; project?: Project }) { const { agentStatus, project } = props; const [pendingAction, setPendingAction] = useState<"login" | "refresh" | undefined>(); @@ -60,12 +71,28 @@ export function ThreadAuthRequiredDock(props: { agentStatus: AgentStatus; projec } if (agentStatus.loginCommand) { - runAgentLoginCommand({ + setPendingAction("login"); + const opened = runAgentLoginCommand({ label: agentStatus.label, command: agentStatus.loginCommand, ...(terminalAuthMethod?.env ? { env: terminalAuthMethod.env } : {}), ...(project ? { project } : {}), + onCommandComplete: (exitCode) => { + void refreshAgentStatus(agentStatus) + .then(() => { + if (exitCode === 0) toast.success(`${agentStatus.label} authenticated.`); + }) + .catch((error: unknown) => { + toast.danger( + error instanceof Error + ? error.message + : `Unable to refresh ${agentStatus.label} status.`, + ); + }) + .finally(() => setPendingAction(undefined)); + }, }); + if (!opened) setPendingAction(undefined); } } @@ -94,10 +121,11 @@ export function ThreadAuthRequiredDock(props: { agentStatus: AgentStatus; projec {hasDirectLogin ? (