From 047c96855e1ce0bbd14fdf01fcd696b7d2fafe76 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 10 Nov 2026 18:25:46 -0800 Subject: [PATCH] feat(cloud): RA-11 strip over a running Orca Cloud agent whose sign-in expires; strip reserves its height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - When the Orca account goes from active/expiring to expired/revoked, every running Orca Cloud Claude/Codex pane shows the strip (same component, place, height as #315): 'Your Claude sign-in expired' / 'Renew to keep working. Your conversation is kept.' / Renew (compact: 'Claude sign-in expired' + Renew). The process keeps running; nothing is parsed from terminal output. - One Renew clears state 1 everywhere; the panes that showed it move to state 2: 'Claude sign-in renewed' / 'Restart Claude to pick up where you left off.' (or 'Starts a new conversation.') / Restart Claude / Dismiss. Restart reuses the account-restart path (queuePaneAccountRestarts), which resumes the pane's own session. Never automatic; a turn that ends cleanly after the renewal clears it. - Kill switch / never offered: nothing new on a running pane. A held launch keeps #315's strip. - Account reads while a running cloud agent pane is mounted: on window focus and at most once a minute (one timer per window). - #315 P2: the strip is a pane-top banner (useReservePaneTopSpace), so the terminal refits below it and no row is covered; a spacer keeps it under a chromeless pane's title-row buttons. - Spec: terminal starts below the strip and its last row stays visible (held and running); state 2's Dismiss sits a full title row below the pane's ×. Removes the debug screenshot left in #315's spec and polls the resize assertion (it raced the layout). --- .../CloudAgentSignInStrip.fixture.tsx | 63 +++++-- .../CloudAgentSignInStrip.tsx | 17 +- .../CloudAgentSignInStripPortals.tsx | 14 +- .../CloudRunningStripPortal.tsx | 112 +++++++++++++ .../cloud-agent-strip-model.ts | 4 + .../cloud-running-strip-model.ts | 52 ++++++ .../pane-top-banner/PaneTopWarningBanner.tsx | 2 +- src/renderer/src/i18n/locales/en.json | 11 +- .../cloud-running-agent-signout.test.ts | 113 +++++++++++++ .../orca-cloud/cloud-running-agent-signout.ts | 157 ++++++++++++++++++ .../orca-cloud-agent-accounts-store.ts | 5 + .../cloud-agent-sign-in-strip.spec.ts | 52 +++++- 12 files changed, 573 insertions(+), 29 deletions(-) create mode 100644 src/renderer/src/components/agent-sign-in-strip/CloudRunningStripPortal.tsx create mode 100644 src/renderer/src/components/agent-sign-in-strip/cloud-running-strip-model.ts create mode 100644 src/renderer/src/lib/orca-cloud/cloud-running-agent-signout.test.ts create mode 100644 src/renderer/src/lib/orca-cloud/cloud-running-agent-signout.ts diff --git a/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.fixture.tsx b/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.fixture.tsx index 5edbf4a35e8..c1cb0d8be72 100644 --- a/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.fixture.tsx +++ b/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.fixture.tsx @@ -11,6 +11,8 @@ import { type CloudStripActions } from './CloudAgentSignInStrip' import { cloudAgentStripModel } from './cloud-agent-strip-model' +import { cloudRunningStripModel } from './cloud-running-strip-model' +import type { CloudRunningStrip } from '@/lib/orca-cloud/cloud-running-agent-signout' const noop = (): void => {} const ACTIONS: CloudStripActions = { @@ -24,29 +26,34 @@ const ACTIONS: CloudStripActions = { const PROMPT = 'Fix the flaky auth test and open a PR' function Pane({ - reason, + reason = 'accountExpired', view = null, agent = 'claude', - width = 900 + width = 900, + running }: { - reason: CloudAgentStripReason + reason?: CloudAgentStripReason view?: AgentSignInGateView | null agent?: HostAgentSignInAgent width?: number + /** RA-11: the strip over a running agent instead of a held launch. */ + running?: CloudRunningStrip }): React.JSX.Element { - const model = cloudAgentStripModel({ - agent, - reason, - view, - hostLabel: translate('agentSignInStrip.sandbox', 'the sandbox'), - t: translate - }) + const model = running + ? cloudRunningStripModel(running, translate) + : cloudAgentStripModel({ + agent, + reason, + view, + hostLabel: translate('agentSignInStrip.sandbox', 'the sandbox'), + t: translate + }) return (
{/* Stands in for a terminal pane: its title row (split, close) stays above the strip. */}
@@ -64,14 +71,21 @@ function Pane({ -
-          ~/acme/web $
-        
+ {/* The terminal: it starts below the strip (reserved, never covered) and keeps its rows. */} +
+ {running ? ( + <> +
⏺ Reading src/auth/session.ts…
+
⏺ API Error: 401 · Not logged in · Run /login
+ + ) : null} +
~/acme/web $
+
) @@ -128,3 +142,22 @@ export const waitingNarrow = fixture({ export const needExpiredNarrow = fixture({ reason: 'accountExpired', width: 520 }) /** Below 360 px: one line and Renew; the detail stays the region's description. */ export const needExpiredCompact = fixture({ reason: 'accountExpired', width: 320 }) + +/** RA-11 state 1: Claude was running when its Orca-account sign-in expired. */ +export const runningExpired = fixture({ running: { phase: 'expired', agent: 'claude' } }) +export const runningExpiredCompact = fixture({ + running: { phase: 'expired', agent: 'claude' }, + width: 320 +}) +/** RA-11 state 2: renewed; this pane's agent still needs a restart (resumes its conversation). */ +export const runningRenewed = fixture({ + running: { phase: 'renewed', agent: 'claude', resumable: true } +}) +/** RA-11 state 2 when the session can't be resumed. */ +export const runningRenewedFresh = fixture({ + running: { phase: 'renewed', agent: 'claude', resumable: false } +}) +export const runningRenewedNarrow = fixture({ + running: { phase: 'renewed', agent: 'claude', resumable: true }, + width: 520 +}) diff --git a/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.tsx b/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.tsx index 42db438513f..ff6ab3ba3e4 100644 --- a/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.tsx +++ b/src/renderer/src/components/agent-sign-in-strip/CloudAgentSignInStrip.tsx @@ -23,6 +23,7 @@ import { } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' +import { useReservePaneTopSpace } from '../terminal-pane/appearance/pane-top-banner/PaneTopWarningBanner' import type { CloudStripModel, CloudStripPrimary, @@ -69,11 +70,15 @@ function useLiveStripWidth( * buttons stay visible and clickable while it shows (RA-1). The pane and its fixture share it. */ export function CloudStripLayer({ children }: { children: React.ReactNode }): React.JSX.Element { + // A pane-top banner (RA-11): the pane reserves its height and the terminal refits below it, so + // no row sits under the strip. The spacer keeps it below a chromeless pane's title-row buttons. + const ref = useReservePaneTopSpace() return ( -
+
+
{children}
) @@ -282,12 +287,12 @@ export function CloudAgentSignInStrip({ ) : null} {/* Renew is the whole action and the strip leaves on its own; Escape still cancels. */} - {model.status === 'done' || primary?.action.kind === 'renew' ? null : ( + {!model.dismiss && (model.status === 'done' || primary?.action.kind === 'renew') ? null : (