feat(cloud): CHAT-RENEW-INPLACE every expired strip renews in place (held, running terminal, chat); idle-chat strip and send block; fixtures

This commit is contained in:
Neil
2026-11-12 23:37:35 -08:00
parent f5e9547bee
commit e95d45c0e6
14 changed files with 587 additions and 30 deletions
@@ -18,6 +18,7 @@ import { Button } from '@/components/ui/button'
import type { ExternalLinkSource } from '@/components/open-external/copy-external-link'
import { CloudStripPasteForm } from './CloudStripPasteForm'
import { StripPrimaryButton, stripPrimaryLink } from './CloudStripPrimaryButton'
import { CloudStripSettingsLink } from './CloudStripSettingsLink'
import {
DropdownMenu,
DropdownMenuContent,
@@ -103,6 +104,8 @@ export type CloudStripActions = {
* setup's sign-in page, known only to main). Actions carrying a `url` copy it without this.
*/
linkFor?: (action: CloudStripPrimary) => ExternalLinkSource | null
/** CHAT-RENEW-INPLACE: the model's `settingsLink` ("Renew in Settings"). */
openSettings?: () => void
}
/** GA-P3t: the strip's severity look; anything that isn't a warning or an error reads as info. */
@@ -336,6 +339,9 @@ export function CloudAgentSignInStrip({
</DropdownMenuContent>
</DropdownMenu>
) : null}
{model.settingsLink && !stacked ? (
<CloudStripSettingsLink label={model.settingsLink} onOpen={actions.openSettings} />
) : null}
{/* Renew and Restart are the whole action and the strip leaves on its own; Escape still cancels. */}
{model.noClose ||
(!model.dismiss &&
@@ -378,6 +384,13 @@ export function CloudAgentSignInStrip({
{model.primary.label}
</StripPrimaryButton>
) : null}
{model.settingsLink ? (
<CloudStripSettingsLink
ownLine
label={model.settingsLink}
onOpen={actions.openSettings}
/>
) : null}
</div>
) : null}
<span role="status" className="sr-only">
@@ -0,0 +1,136 @@
// CHAT-RENEW-INPLACE: the in-place renewal, step by step, on all three surfaces (a launch held on the
// expired sign-in, a running terminal, a structured chat). design-squad's per-step copy.
import React from 'react'
import { defineFixture } from '../../component-fixtures/component-fixture'
import { translate } from '@/i18n/i18n'
import type { OrcaCloudAgentSetup } from '../../../../shared/orca-cloud-agents/orca-cloud-agents-wire'
import {
CloudAgentSignInStrip,
CloudStripLayer,
cloudStripWidth,
type CloudStripActions
} from './CloudAgentSignInStrip'
import type { CloudStripModel } from './cloud-agent-strip-model'
import { cloudAgentStripModel } from './cloud-agent-strip-model'
import { cloudAgentAwaitingStartStripModel } from './cloud-agent-setup-strip-model'
import { cloudChatRunningStripModel, cloudRunningStripModel } from './cloud-running-strip-model'
import { cloudRenewInPlaceModel } from './cloud-renew-in-place'
const noop = (): void => {}
const ACTIONS: CloudStripActions = {
primary: noop,
submitCode: noop,
pasteCode: noop,
useCode: noop,
useApiKey: noop,
cancel: noop,
openSettings: noop
}
type Surface = 'held' | 'terminal' | 'chat'
type Step = 'expired' | 'browser' | 'checking' | 'rejected' | 'renewed'
const SETUP: Record<Exclude<Step, 'renewed'>, OrcaCloudAgentSetup> = {
expired: { phase: 'idle' },
browser: { phase: 'browser', startedAt: 0, canReopen: true, acceptsCode: true },
checking: {
phase: 'browser',
startedAt: 0,
canReopen: true,
acceptsCode: true,
code: 'checking'
},
rejected: { phase: 'browser', startedAt: 0, canReopen: true, acceptsCode: true, code: 'rejected' }
}
function expiredModel(surface: Surface): CloudStripModel {
const strip = { phase: 'expired' as const, agent: 'claude' as const }
return surface === 'held'
? cloudAgentStripModel({
agent: 'claude',
reason: 'accountExpired',
view: null,
hostLabel: '',
t: translate
})
: surface === 'chat'
? cloudChatRunningStripModel(strip, translate)
: cloudRunningStripModel(strip, translate)
}
function stepModel(surface: Surface, step: Step): CloudStripModel {
if (step !== 'renewed') {
return cloudRenewInPlaceModel({
agent: 'claude',
expired: expiredModel(surface),
setup: SETUP[step],
t: translate
})
}
const renewed = { phase: 'renewed' as const, agent: 'claude' as const, resumable: true }
// Step 5 is each surface's own: held waits for Start (team-lead B), the terminal offers Restart
// (RA-11), the chat's next message uses the renewed sign-in.
return surface === 'held'
? cloudAgentAwaitingStartStripModel({
agent: 'claude',
awaiting: 'renewed',
queuedPrompt: true,
t: translate
})
: surface === 'chat'
? cloudChatRunningStripModel(renewed, translate)
: cloudRunningStripModel(renewed, translate)
}
function Frame({
surface,
step,
width
}: {
surface: Surface
step: Step
width: number
}): React.JSX.Element {
return (
<div className="bg-background p-4">
<div
className="relative h-[220px] overflow-hidden rounded-md border border-border bg-background"
style={{ width }}
>
<CloudStripLayer>
<CloudAgentSignInStrip
model={stepModel(surface, step)}
prompt={surface === 'held' ? 'Fix the flaky auth test and open a PR' : null}
width={cloudStripWidth(width)}
actions={ACTIONS}
/>
</CloudStripLayer>
</div>
</div>
)
}
const fixture = (surface: Surface, step: Step, width = 900) =>
defineFixture({
width: width + 40,
render: () => <Frame surface={surface} step={step} width={width} />
})
export const heldExpired = fixture('held', 'expired')
export const heldBrowser = fixture('held', 'browser')
export const heldChecking = fixture('held', 'checking')
export const heldRejected = fixture('held', 'rejected')
export const heldRenewed = fixture('held', 'renewed')
export const terminalExpired = fixture('terminal', 'expired')
export const terminalBrowser = fixture('terminal', 'browser')
export const terminalChecking = fixture('terminal', 'checking')
export const terminalRejected = fixture('terminal', 'rejected')
export const terminalRenewed = fixture('terminal', 'renewed')
export const chatExpired = fixture('chat', 'expired')
export const chatBrowser = fixture('chat', 'browser')
export const chatChecking = fixture('chat', 'checking')
export const chatRejected = fixture('chat', 'rejected')
export const chatRenewed = fixture('chat', 'renewed')
/** Narrow: the code field stacks under the text, and "Renew in Settings" gets its own line. */
export const chatBrowserNarrow = fixture('chat', 'browser', 440)
export const terminalRejectedNarrow = fixture('terminal', 'rejected', 440)
@@ -49,7 +49,14 @@ vi.mock('@/lib/orca-cloud/orca-cloud-environment-id', () => ({
}))
vi.mock('@/lib/orca-cloud/orca-cloud-agent-accounts-store', () => ({
orcaCloudAgentAccountsLoadedAt: () => 0,
useOrcaCloudAgentAccounts: () => ({ load: { kind: 'loading' }, keyKinds: {} })
useOrcaCloudAgentAccounts: () => ({
load: { kind: 'loading' },
keyKinds: {},
setups: { claude: { phase: 'idle' }, codex: { phase: 'idle' } }
}),
runOrcaCloudAgentSetups: () => undefined,
stopOrcaCloudAgentSetups: () => undefined,
reloadOrcaCloudAgentAccounts: async () => undefined
}))
vi.mock('@/lib/orca-cloud/cloud-running-agent-signout', () => ({
clearCloudRunningSignout: () => undefined,
@@ -42,6 +42,7 @@ import {
cloudRestartFreshModel,
cloudRunningStripModel
} from './cloud-running-strip-model'
import { openOrcaCloudAccountsSettings, withRenewInPlace } from './cloud-renew-in-place'
const NO_ACTIONS: CloudStripActions = {
primary: () => undefined,
@@ -52,12 +53,6 @@ const NO_ACTIONS: CloudStripActions = {
cancel: () => undefined
}
function openAccounts(): void {
const store = useAppStore.getState()
store.openSettingsTarget({ pane: 'accounts', repoId: null, sectionId: 'orca-cloud' })
store.openSettingsPage()
}
export function CloudRunningStripPortal({
pane,
tabId,
@@ -192,7 +187,7 @@ export function CloudRunningStripPortal({
const actions: CloudStripActions = {
primary: (action) => {
if (action.kind === 'renew') {
openAccounts()
openOrcaCloudAccountsSettings()
} else if (action.kind === 'restart' && strip.phase === 'renewed') {
restart({ agent: strip.agent, resumable: strip.resumable })
}
@@ -204,6 +199,19 @@ export function CloudRunningStripPortal({
// State 2's ×; state 1 has none (Renew is the action) and Escape leaves it showing.
cancel: () => (strip.phase === 'renewed' ? clearCloudRunningSignout(paneKey) : undefined)
}
// CHAT-RENEW-INPLACE: "sign-in expired · Renew" renews right here; renewed keeps RA-11's Restart.
const model = cloudRunningStripModel(strip, t)
const shown =
strip.phase === 'expired'
? withRenewInPlace({
agent: strip.agent,
setup: accounts.setups[strip.agent],
model,
actions,
key: paneKey,
t
})
: { model, actions }
return (
<CloudPaneBannerPortal
pane={pane}
@@ -212,10 +220,10 @@ export function CloudRunningStripPortal({
stripLayer
>
<CloudAgentSignInStrip
model={cloudRunningStripModel(strip, t)}
model={shown.model}
prompt={null}
width={cloudStripWidth(pane.container.clientWidth)}
actions={actions}
actions={shown.actions}
/>
</CloudPaneBannerPortal>
)
@@ -0,0 +1,34 @@
import React from 'react'
import { cn } from '@/lib/class-names'
/**
* CHAT-RENEW-INPLACE: the strip's quiet "Renew in Settings" link, at the end of the actions row, or on
* its own line under the buttons in the narrow stack (design-squad).
*/
export function CloudStripSettingsLink({
label,
onOpen,
ownLine = false
}: {
label: string
onOpen?: () => void
ownLine?: boolean
}): React.JSX.Element | null {
if (!onOpen) {
return null
}
return (
<button
type="button"
data-strip-settings-link
onClick={onOpen}
className={cn(
'shrink-0 cursor-pointer text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline',
'rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring',
ownLine && 'basis-full text-left'
)}
>
{label}
</button>
)
}
@@ -5,11 +5,11 @@ import { cloudAgentStripModel } from './cloud-agent-strip-model'
const base = { agent: 'claude' as const, hostLabel: 'the sandbox', t: translate }
describe('cloudAgentStripModel', () => {
it('account recovery (S4-c): Renew, account-level wording, no per-workspace words', () => {
it('account recovery (S4-c), held: Renew, "Renew to start Claude." (CHAT-RENEW-INPLACE copy)', () => {
const model = cloudAgentStripModel({ ...base, reason: 'accountExpired', view: null })
expect(model).toMatchObject({
title: 'Your Claude sign-in expired',
detail: 'Sign in once to fix every workspace.',
detail: 'Renew to start Claude.',
primary: { label: 'Renew', action: { kind: 'renew' } }
})
})
@@ -57,6 +57,11 @@ export type CloudStripModel = {
otherWays: boolean
/** Already on a code: ⋯ offers the paste fallback and Cancel instead of "Use a code". */
codeStep?: boolean
/**
* CHAT-RENEW-INPLACE: a quiet text link at the end of the actions row ("Renew in Settings"); in
* the narrow stack it gets its own line under the buttons.
*/
settingsLink?: string
/** RA-11 state 2: a dismiss × with this label (the strip otherwise drops its × on Renew). */
dismiss?: string
/** RA-11 "Restarting…": nothing to close; the strip leaves with the result. */
@@ -121,15 +126,15 @@ export function cloudAgentStripModel(args: {
return {
status: 'warning',
title: t('agentSignInStrip.expired', 'Your {{agent}} sign-in expired', { agent }),
detail: t('agentSignInStrip.expiredDetail', 'Sign in once to fix every workspace.'),
detail: t('agentSignInStrip.expiredHeldDetail', 'Renew to start {{agent}}.', { agent }),
description: null,
primary: { label: t('agentSignInStrip.renew', 'Renew'), action: { kind: 'renew' } },
code: null,
paste: null,
otherWays: false,
announcement: t(
'agentSignInStrip.expiredAnnounce',
'Your {{agent}} sign-in expired. Renew it once to fix every workspace.',
'agentSignInStrip.expiredHeldAnnounce',
'Your {{agent}} sign-in expired. Renew to start {{agent}}.',
{ agent }
)
}
@@ -21,7 +21,8 @@ const stub = vi.hoisted(() => {
cleared: string[]
calls: { paneKey: string; turnEndedFallback?: boolean }[]
openedSettings: number
} = { strip: null, held: false, cleared: [], calls: [], openedSettings: 0 }
renewsRun: string[][]
} = { strip: null, held: false, cleared: [], calls: [], openedSettings: 0, renewsRun: [] }
return state
})
@@ -47,6 +48,9 @@ vi.mock('@/lib/orca-cloud/orca-cloud-environment-id', () => ({
}))
vi.mock('@/lib/orca-cloud/orca-cloud-agent-accounts-store', () => ({
orcaCloudAgentAccountsLoadedAt: () => 1,
runOrcaCloudAgentSetups: (agents: string[]) => stub.renewsRun.push(agents),
stopOrcaCloudAgentSetups: () => undefined,
reloadOrcaCloudAgentAccounts: async () => undefined,
useOrcaCloudAgentAccounts: () => ({
load: { kind: 'loading' },
keyKinds: {},
@@ -118,7 +122,9 @@ describe('the running strip on a structured chat (B2)', () => {
mount()
expect(screen.getAllByText(/Claude sign-in expired/).length).toBeGreaterThan(0)
fireEvent.click(screen.getByRole('button', { name: /Renew/ }))
expect(stub.openedSettings).toBe(1)
// CHAT-RENEW-INPLACE: Renew runs the renewal right here, never Settings.
expect(stub.renewsRun).toEqual([['claude']])
expect(stub.openedSettings).toBe(0)
expect(stub.calls.at(-1)).toEqual({ paneKey: STATUS_KEY, turnEndedFallback: true })
})
@@ -0,0 +1,153 @@
// CHAT-RENEW-INPLACE: every expired strip renews in place, with design-squad's per-step copy.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { OrcaCloudAgentSetup } from '../../../../shared/orca-cloud-agents/orca-cloud-agents-wire'
import type { CloudStripActions } from './CloudAgentSignInStrip'
import type { CloudStripModel } from './cloud-agent-strip-model'
import { translate as t } from '@/i18n/i18n'
const store = vi.hoisted(() => ({ run: vi.fn(), stop: vi.fn() }))
vi.mock('@/lib/orca-cloud/orca-cloud-agent-accounts-store', () => ({
runOrcaCloudAgentSetups: store.run,
stopOrcaCloudAgentSetups: store.stop,
reloadOrcaCloudAgentAccounts: async () => undefined
}))
vi.mock('@/lib/orca-cloud/cloud-agent-launch-hold', () => ({
releaseCloudAgentHold: vi.fn(),
startCloudAgentHold: vi.fn()
}))
vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } }))
vi.mock('../agent-sign-in/AgentSignInPanePortals', () => ({
agentSignInCardActions: () => ({ copyText: vi.fn(), openLink: vi.fn() })
}))
const api = vi.hoisted(() => ({ reopen: vi.fn(async () => undefined), submitClaudeCode: vi.fn() }))
vi.stubGlobal('window', { api: { orcaCloudAgents: api } })
const { cloudRenewInPlaceActions, cloudRenewInPlaceModel } = await import('./cloud-renew-in-place')
const EXPIRED: CloudStripModel = {
status: 'warning',
title: 'Your Claude sign-in expired',
detail: 'Renew to keep working. Your conversation is kept.',
description: null,
primary: { label: 'Renew', action: { kind: 'renew' } },
code: null,
paste: null,
otherWays: false,
announcement: 'Your Claude sign-in expired.'
}
const model = (setup: OrcaCloudAgentSetup) =>
cloudRenewInPlaceModel({ agent: 'claude', expired: EXPIRED, setup, t })
describe('cloudRenewInPlaceModel (design-squad per-step copy)', () => {
it("1 expired: the surface's own strip, [Renew], no Settings link", () => {
expect(model({ phase: 'idle' })).toBe(EXPIRED)
expect(model({ phase: 'saved' })).toBe(EXPIRED)
})
it('2 browser: same title, paste code, [Finish renewing] [Open again], "Renew in Settings"', () => {
const shown = model({ phase: 'browser', startedAt: 0, canReopen: true, acceptsCode: true })
expect(shown.title).toBe('Your Claude sign-in expired')
expect(shown.detail).toBe('Finish in your browser, then paste the code here.')
expect(shown.paste?.setupCode).toEqual({
placeholder: 'Paste code',
submitLabel: 'Finish renewing'
})
expect(shown.primary).toMatchObject({ label: 'Open again', action: { kind: 'reopenSetup' } })
expect(shown.settingsLink).toBe('Renew in Settings')
expect(JSON.stringify(shown)).not.toContain('Finish setup')
})
it('3 checking: the field and its button disabled', () => {
const shown = model({
phase: 'browser',
startedAt: 0,
canReopen: true,
acceptsCode: true,
code: 'checking'
})
expect(shown.paste).toMatchObject({ submitting: true })
expect(shown.paste?.setupCode?.submitLabel).toBe('Checking the code…')
})
it('4 rejected: "Code didn\'t work", the newest-code detail, [Open again] and the Settings link', () => {
const shown = model({
phase: 'browser',
startedAt: 0,
canReopen: true,
acceptsCode: true,
code: 'rejected'
})
expect(shown.title).toBe('Code didn’t work')
expect(shown.detail).toBe('Paste the newest code from the sign-in page, or open it again.')
expect(shown.paste?.refusal).toBeTruthy()
expect(shown.primary?.action.kind).toBe('reopenSetup')
expect(shown.settingsLink).toBe('Renew in Settings')
})
it('a setup that failed outright starts again from Renew', () => {
const shown = model({ phase: 'failed', error: 'denied' })
expect(shown.status).toBe('error')
expect(shown.primary?.action.kind).toBe('renew')
})
})
describe('cloudRenewInPlaceActions', () => {
const surface = (): CloudStripActions => ({
primary: vi.fn(),
submitCode: vi.fn(),
pasteCode: vi.fn(),
useCode: vi.fn(),
useApiKey: vi.fn(),
cancel: vi.fn()
})
const actions = (setup: OrcaCloudAgentSetup, own = surface(), openSettings = vi.fn()) => ({
own,
openSettings,
built: cloudRenewInPlaceActions({
agent: 'claude',
setup,
surface: own,
openSettings,
copyText: vi.fn(),
openLink: vi.fn()
})
})
beforeEach(() => {
store.run.mockClear()
store.stop.mockClear()
api.reopen.mockClear()
})
it('Renew runs the setup here (never the Settings page or the sign-in sheet)', () => {
const { built, own, openSettings } = actions({ phase: 'idle' })
built.primary({ kind: 'renew' })
expect(store.run).toHaveBeenCalledExactlyOnceWith(['claude'])
expect(own.primary).not.toHaveBeenCalled()
expect(openSettings).not.toHaveBeenCalled()
})
it('Open again reopens the setup page; "Renew in Settings" opens Settings', () => {
const { built, openSettings } = actions({ phase: 'browser', startedAt: 0, canReopen: true })
built.primary({ kind: 'reopenSetup' })
expect(api.reopen).toHaveBeenCalledExactlyOnceWith({ agent: 'claude' })
built.openSettings?.()
expect(openSettings).toHaveBeenCalledOnce()
})
it('6: × mid-flow stops the setup (back to step 1) and never dismisses the strip', () => {
const { built, own } = actions({ phase: 'browser', startedAt: 0, canReopen: true })
built.cancel()
expect(store.stop).toHaveBeenCalledExactlyOnceWith('claude')
expect(own.cancel).not.toHaveBeenCalled()
})
it('outside a renewal, the surface keeps its own × and Restart', () => {
const { built, own } = actions({ phase: 'idle' })
built.cancel()
built.primary({ kind: 'restart' })
expect(store.stop).not.toHaveBeenCalled()
expect(own.cancel).toHaveBeenCalledOnce()
expect(own.primary).toHaveBeenCalledExactlyOnceWith({ kind: 'restart' })
})
})
@@ -0,0 +1,159 @@
// CHAT-RENEW-INPLACE (Option A, design-squad per-step copy): every "sign-in expired" strip — a held
// launch, a running terminal, a structured chat — renews in place. Renew runs the same Orca Cloud
// setup as "Set up once" right in the strip (re-running it replaces the account's expired sign-in),
// instead of sending the person to Settings. One model and one set of actions for every surface, so
// the terminal and the chat can't drift apart.
// 1 expired: [Renew] 2 browser: paste code, [Finish renewing] [Open again] ×
// 3 checking: field and buttons disabled 4 rejected: "Code didn't work", [Open again]
// 5 renewed: the surface's own renewed state (held: Start; terminal: Restart; chat: next message)
// × mid-flow stops the setup and goes back to step 1; it never dismisses the strip.
import type { Translate } from '@/i18n/use-translate'
import { useAppStore } from '@/store'
import {
runOrcaCloudAgentSetups,
stopOrcaCloudAgentSetups
} from '@/lib/orca-cloud/orca-cloud-agent-accounts-store'
import type { OrcaCloudAgentSetup } from '../../../../shared/orca-cloud-agents/orca-cloud-agents-wire'
import type { HostAgentSignInAgent } from '../../../../shared/host-agent-sign-in-status'
import type { CloudStripActions } from './CloudAgentSignInStrip'
import type { CloudStripModel } from './cloud-agent-strip-model'
import { cloudAgentSetupStripModel } from './cloud-agent-setup-strip-model'
import { cloudSetupStripActions } from './cloud-setup-strip-actions'
import { agentSignInCardActions } from '../agent-sign-in/AgentSignInPanePortals'
/** The renewal's own setup is under way (steps 2-4): the strip shows it instead of step 1. */
export function renewInPlaceRunning(setup: OrcaCloudAgentSetup): boolean {
return setup.phase !== 'idle' && setup.phase !== 'saved'
}
/**
* Steps 1-4 over a surface's "expired" model (step 1 keeps the surface's own detail: running "Renew
* to keep working…", held "Renew to start {{agent}}."). Step 5 is each surface's renewed state.
*/
export function cloudRenewInPlaceModel(args: {
agent: HostAgentSignInAgent
expired: CloudStripModel
setup: OrcaCloudAgentSetup
t: Translate
}): CloudStripModel {
const { expired, setup, t } = args
if (!renewInPlaceRunning(setup)) {
return expired
}
const flow = cloudAgentSetupStripModel({ agent: args.agent, setup, ready: false, t })
const settingsLink = t('agentSignInStrip.renewInSettings', 'Renew in Settings')
// A failed setup keeps its own words; a refused code says "Code didn't work" (as #484).
const keepsTitle =
setup.phase === 'failed' || (setup.phase === 'browser' && setup.code === 'rejected')
const finishRenewing = t('agentSignInStrip.finishRenewing', 'Finish renewing')
const paste = flow.paste
? {
...flow.paste,
// Never the setup's "Finish setup" (design-squad).
...(flow.paste.setupCode
? {
setupCode: {
...flow.paste.setupCode,
submitLabel:
setup.phase === 'browser' && setup.code === 'checking'
? flow.paste.setupCode.submitLabel
: finishRenewing
}
}
: {}),
...(flow.paste.submitLabel ? { submitLabel: finishRenewing } : {})
}
: null
return {
...flow,
title: keepsTitle ? flow.title : expired.title,
paste,
// A setup that failed outright starts again from Renew.
primary:
setup.phase === 'failed' && flow.primary?.action.kind === 'setUpOnce'
? { ...flow.primary, action: { kind: 'renew' } }
: flow.primary,
...(setup.phase === 'browser' || setup.phase === 'paste' ? { settingsLink } : {}),
announcement: keepsTitle ? flow.announcement : `${expired.title}. ${flow.announcement}`
}
}
/**
* Renew starts the setup here; Open again, the code and the paste go to it; × stops it (back to
* step 1). With no renewal running, everything else is the surface's own (its × or Restart).
*/
export function cloudRenewInPlaceActions(args: {
agent: HostAgentSignInAgent
setup: OrcaCloudAgentSetup
/** The surface's own actions, used outside the renewal (Restart, a renewed strip's ×). */
surface: CloudStripActions
/** "Renew in Settings": the same renewal in Settings › Accounts › Orca Cloud. */
openSettings: () => void
copyText: (text: string) => void
openLink: (url: string) => void
}): CloudStripActions {
const running = renewInPlaceRunning(args.setup)
const flow = cloudSetupStripActions({
agent: args.agent,
// Renewing holds nothing itself: Start (step 5) belongs to the surface.
holdKey: '',
setupRunning: running,
copyText: args.copyText,
openLink: args.openLink,
useApiKey: args.surface.useApiKey,
keepHoldOnCancel: true
})
return {
...args.surface,
primary: (action) => {
if (action.kind === 'renew') {
runOrcaCloudAgentSetups([args.agent])
} else if (action.kind === 'reopenSetup' || action.kind === 'copyAndOpen') {
flow.primary(action)
} else {
args.surface.primary(action)
}
},
submitCode: running ? flow.submitCode : args.surface.submitCode,
submitSetupCode: flow.submitSetupCode,
pasteCode: running ? flow.pasteCode : args.surface.pasteCode,
openSettings: args.openSettings,
cancel: () => (running ? stopOrcaCloudAgentSetups(args.agent) : args.surface.cancel())
}
}
/** "Renew in Settings": Settings › Accounts › Orca Cloud, where the same renewal runs. */
export function openOrcaCloudAccountsSettings(): void {
const store = useAppStore.getState()
store.openSettingsTarget({ pane: 'accounts', repoId: null, sectionId: 'orca-cloud' })
store.openSettingsPage()
}
/** A surface's expired strip, renewing in place: its model (steps 1-4) and actions together. */
export function withRenewInPlace(args: {
agent: HostAgentSignInAgent
setup: OrcaCloudAgentSetup
model: CloudStripModel
actions: CloudStripActions
/** The pane or chat key, for the sign-in page's copy and open. */
key: string
t: Translate
}): { model: CloudStripModel; actions: CloudStripActions } {
const page = agentSignInCardActions(args.key)
return {
model: cloudRenewInPlaceModel({
agent: args.agent,
expired: args.model,
setup: args.setup,
t: args.t
}),
actions: cloudRenewInPlaceActions({
agent: args.agent,
setup: args.setup,
surface: args.actions,
openSettings: openOrcaCloudAccountsSettings,
copyText: page.copyText,
openLink: page.openLink
})
}
}
@@ -31,7 +31,7 @@ export function cloudSetupStripActions(args: {
keepHoldOnCancel?: boolean
}): CloudStripActions {
const { agent, holdKey } = args
const api = window.api.orcaCloudAgents
const api = window.api?.orcaCloudAgents
return {
primary: (action) => {
if (action.kind === 'setUpOnce') {
@@ -26,12 +26,7 @@ import {
import type { CloudStripActions } from './CloudAgentSignInStrip'
import type { CloudHeldStrip } from './use-cloud-held-strip'
import { cloudChatRunningStripModel } from './cloud-running-strip-model'
function openAccounts(): void {
const store = useAppStore.getState()
store.openSettingsTarget({ pane: 'accounts', repoId: null, sectionId: 'orca-cloud' })
store.openSettingsPage()
}
import { openOrcaCloudAccountsSettings, withRenewInPlace } from './cloud-renew-in-place'
/** The Orca Cloud environment a structured chat's tab runs on, or null. */
function useChatEnvironmentId(tabId: string): string | null {
@@ -94,7 +89,8 @@ export function useCloudChatAccountStrip(args: ChatAccountArgs): CloudRunningStr
}
return cloudRunningStrip({
paneKey: args.statusKey,
entry: status ?? { agentType: agent, state: 'idle' },
// An idle chat (no message yet) has no status entry: decided from its own agent.
entry: status ?? { agentType: agent, state: 'waiting' },
accounts,
held,
now: orcaCloudAgentAccountsLoadedAt(),
@@ -108,11 +104,12 @@ export function useCloudChatAccountStrip(args: ChatAccountArgs): CloudRunningStr
export function useCloudChatRunningStrip(args: ChatAccountArgs): CloudHeldStrip | null {
const t = useTranslate()
const strip = useCloudChatAccountStrip(args)
const accounts = useOrcaCloudAgentAccounts(strip !== null)
if (!strip) {
return null
}
const actions: CloudStripActions = {
primary: (action) => (action.kind === 'renew' ? openAccounts() : undefined),
primary: (action) => (action.kind === 'renew' ? openOrcaCloudAccountsSettings() : undefined),
submitCode: () => undefined,
pasteCode: () => undefined,
useCode: () => undefined,
@@ -120,7 +117,20 @@ export function useCloudChatRunningStrip(args: ChatAccountArgs): CloudHeldStrip
// Expired has no ×; renewed's × dismisses.
cancel: () => (strip.phase === 'renewed' ? clearCloudRunningSignout(args.statusKey) : undefined)
}
return { model: cloudChatRunningStripModel(strip, t), prompt: null, actions }
const model = cloudChatRunningStripModel(strip, t)
// CHAT-RENEW-INPLACE: the chat's "sign-in expired · Renew" renews right here.
const shown =
strip.phase === 'expired'
? withRenewInPlace({
agent: strip.agent,
setup: accounts.setups[strip.agent],
model,
actions,
key: args.statusKey,
t
})
: { model, actions }
return { ...shown, prompt: null }
}
/**
@@ -21,6 +21,11 @@ import {
cloudAgentSetupStripModel
} from './cloud-agent-setup-strip-model'
import { cloudSetupStripActions } from './cloud-setup-strip-actions'
import {
cloudRenewInPlaceActions,
cloudRenewInPlaceModel,
openOrcaCloudAccountsSettings
} from './cloud-renew-in-place'
export type CloudHeldStrip = {
model: CloudStripModel
@@ -70,7 +75,7 @@ export function useCloudHeldStrip(
const setupHold = hold?.reason === 'notSetUp' || awaiting !== null
const setup = accounts.setups[agent]
// HELD-STALE-COVERED: the setup just finished here: "is set up · Start" (the account, read fresh).
const model =
const shownBeforeRenew =
awaiting === 'setUp'
? cloudAgentSetupStripModel({ agent, setup: accounts.setups[agent], ready: true, t })
: awaiting
@@ -95,6 +100,11 @@ export function useCloudHeldStrip(
hostLabel: cloudGate?.hostLabel ?? t('agentSignInStrip.sandbox', 'the sandbox'),
t
})
// CHAT-RENEW-INPLACE: a launch held on an expired sign-in renews right here (steps 1-4).
const renewing = hold?.reason === 'accountExpired'
const model = renewing
? cloudRenewInPlaceModel({ agent, expired: shownBeforeRenew, setup, t })
: shownBeforeRenew
const setupRunning = setup.phase !== 'idle' && setup.phase !== 'saved' && setup.phase !== 'failed'
const holdActions: CloudStripActions | null = setupHold
? cloudSetupStripActions({
@@ -107,7 +117,7 @@ export function useCloudHeldStrip(
keepHoldOnCancel: chat
})
: null
const actions: CloudStripActions = holdActions ?? {
const gateOrRecovery: CloudStripActions = {
primary: (action) => {
// RA-11's restart belongs to the running-pane strip; a held launch never offers it.
if (
@@ -152,9 +162,21 @@ export function useCloudHeldStrip(
}
}
}
const actions: CloudStripActions =
holdActions ??
(renewing
? cloudRenewInPlaceActions({
agent,
setup,
surface: gateOrRecovery,
openSettings: openOrcaCloudAccountsSettings,
copyText: gateActions.copyText,
openLink: gateActions.openLink
})
: gateOrRecovery)
// A held chat's strip has nothing to cancel unless a setup is running: no × (Escape is a no-op).
const shownModel =
chat && hold && !(setupHold && setupRunning) ? { ...model, noClose: true } : model
chat && hold && !((setupHold || renewing) && setupRunning) ? { ...model, noClose: true } : model
return {
key,
active,
+4
View File
@@ -29017,6 +29017,10 @@
"nothingNeedsAttention": "Nothing needs attention."
},
"agentSignInStrip": {
"renewInSettings": "Renew in Settings",
"finishRenewing": "Finish renewing",
"expiredHeldDetail": "Renew to start {{agent}}.",
"expiredHeldAnnounce": "Your {{agent}} sign-in expired. Renew to start {{agent}}.",
"rowNeedsSignIn": "Needs sign-in",
"rowNeedsSignInTooltip": "Set up once for Orca Cloud",
"rowExpired": "Sign-in expired",