Files
orcs-code/src/commands/login/login.tsx
Anandan 116cc8e6bd Route third-party first-run setup into the provider wizard (#261)
The login picker previously sent third-party users to a dead-end info screen
that only mentioned env vars. This change reuses the existing provider wizard
from the login flow so first-run setup can continue without requiring slash
command access first.

Constraint: The existing provider setup logic must remain the single source of truth
Rejected: Build a separate third-party auth wizard in ConsoleOAuthFlow | would duplicate provider setup behavior and drift over time
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep third-party onboarding routed through ProviderWizard unless the provider command flow is intentionally redesigned
Tested: bun test src/components/ConsoleOAuthFlow.test.tsx src/commands/provider/provider.test.tsx
Tested: tsc --noEmit via project diagnostics
Not-tested: Live gh-authenticated push and PR creation path

Co-authored-by: anandh8x <test@example.com>
2026-04-03 21:18:00 +08:00

133 lines
4.0 KiB
TypeScript

import { feature } from 'bun:bundle'
import * as React from 'react'
import { resetCostState } from '../../bootstrap/state.js'
import {
clearTrustedDeviceToken,
enrollTrustedDevice,
} from '../../bridge/trustedDevice.js'
import type { LocalJSXCommandContext } from '../../commands.js'
import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'
import {
ConsoleOAuthFlow,
type ConsoleOAuthFlowResult,
} from '../../components/ConsoleOAuthFlow.js'
import { Dialog } from '../../components/design-system/Dialog.js'
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'
import { Text } from '../../ink.js'
import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js'
import { refreshPolicyLimits } from '../../services/policyLimits/index.js'
import { refreshRemoteManagedSettings } from '../../services/remoteManagedSettings/index.js'
import type { LocalJSXCommandOnDone } from '../../types/command.js'
import { stripSignatureBlocks } from '../../utils/messages.js'
import {
checkAndDisableAutoModeIfNeeded,
checkAndDisableBypassPermissionsIfNeeded,
resetAutoModeGateCheck,
resetBypassPermissionsCheck,
} from '../../utils/permissions/bypassPermissionsKillswitch.js'
import { resetUserCache } from '../../utils/user.js'
type LoginCompletion =
| ConsoleOAuthFlowResult
| {
type: 'cancel'
}
export async function call(
onDone: LocalJSXCommandOnDone,
context: LocalJSXCommandContext,
): Promise<React.ReactNode> {
return (
<Login
onDone={async result => {
if (result.type === 'cancel') {
onDone('Login interrupted')
return
}
if (result.type === 'provider-setup') {
onDone(result.message, { display: 'system' })
return
}
context.onChangeAPIKey()
// Signature-bearing blocks (thinking, connector_text) are bound to the
// API key. Strip them so the new key doesn't reject stale signatures.
context.setMessages(stripSignatureBlocks)
// Post-login refresh logic. Keep in sync with onboarding in
// src/interactiveHelpers.tsx.
resetCostState()
void refreshRemoteManagedSettings()
void refreshPolicyLimits()
resetUserCache()
refreshGrowthBookAfterAuthChange()
// Clear any stale trusted device token from a previous account before
// re-enrolling to avoid sending the old token while enrollment is
// in flight.
clearTrustedDeviceToken()
void enrollTrustedDevice()
resetBypassPermissionsCheck()
const appState = context.getAppState()
void checkAndDisableBypassPermissionsIfNeeded(
appState.toolPermissionContext,
context.setAppState,
)
if (feature('TRANSCRIPT_CLASSIFIER')) {
resetAutoModeGateCheck()
void checkAndDisableAutoModeIfNeeded(
appState.toolPermissionContext,
context.setAppState,
appState.fastMode,
)
}
context.setAppState(prev => ({
...prev,
authVersion: prev.authVersion + 1,
}))
onDone('Login successful')
}}
/>
)
}
export function Login(props: {
onDone: (result: LoginCompletion, mainLoopModel: string) => void
startingMessage?: string
}): React.ReactNode {
const mainLoopModel = useMainLoopModel()
return (
<Dialog
title="Login"
onCancel={() => props.onDone({ type: 'cancel' }, mainLoopModel)}
color="permission"
inputGuide={exitState =>
exitState.pending ? (
<Text>Press {exitState.keyName} again to exit</Text>
) : (
<ConfigurableShortcutHint
action="confirm:no"
context="Confirmation"
fallback="Esc"
description="cancel"
/>
)
}
>
<ConsoleOAuthFlow
onDone={result =>
props.onDone(result ?? { type: 'cancel' }, mainLoopModel)
}
startingMessage={props.startingMessage}
/>
</Dialog>
)
}