fix(auth): mount login dialog input for paste-code fallback URL
Paste-code OAuth providers (Codex, Anthropic, Gemini CLI, GitLab Duo, Antigravity, Devin) need the user to paste the fallback redirect URL when the loopback callback cannot complete (headless/remote/Windows). The login dialog took focus and cleared the editor but only rendered the auth URL plus a tip pointing at `/login <redirect URL>` — a command only reachable through the now-hidden, unfocused editor. The dialog never mounted an Input, so a pasted URL was silently dropped and login stalled. Route onManualCodeInput through the focused dialog's showManualInput so the paste lands in a visible, submittable field. Make showManualInput idempotent so the OAuth callback retry loop reuses the mounted input instead of stacking duplicate prompts. Fixes #5339
This commit is contained in:
@@ -82,7 +82,7 @@ import { UserMessageSelectorComponent } from "../components/user-message-selecto
|
||||
import type { SessionObserverRegistry } from "../session-observer-registry";
|
||||
import { buildCopyTargets } from "../utils/copy-targets";
|
||||
|
||||
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
||||
const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL), then press Enter:";
|
||||
|
||||
export class SelectorController {
|
||||
constructor(private ctx: InteractiveModeContext) {}
|
||||
@@ -1265,7 +1265,6 @@ export class SelectorController {
|
||||
*/
|
||||
async #handleOAuthLogin(providerId: string): Promise<boolean> {
|
||||
this.ctx.showStatus(`Logging in to ${providerId}…`);
|
||||
const manualInput = this.ctx.oauthManualInput;
|
||||
const useManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(providerId);
|
||||
let restored = false;
|
||||
const restoreEditor = () => {
|
||||
@@ -1293,16 +1292,19 @@ export class SelectorController {
|
||||
// The dialog renders the full URL (SSH-safe copy target) and
|
||||
// opens the browser best-effort.
|
||||
dialog.showAuth(info.url, info.instructions, info.launchUrl);
|
||||
if (useManualInput) {
|
||||
dialog.showProgress(MANUAL_LOGIN_TIP);
|
||||
}
|
||||
},
|
||||
onPrompt: (prompt: { message: string; placeholder?: string }) =>
|
||||
dialog.showPrompt(prompt.message, prompt.placeholder),
|
||||
onProgress: (message: string) => {
|
||||
dialog.showProgress(message);
|
||||
},
|
||||
onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
|
||||
// Paste-code providers (e.g. Codex) may need the user to paste the
|
||||
// fallback redirect URL when the loopback callback can't complete
|
||||
// (headless/remote/Windows). Mount a focused input in the dialog so
|
||||
// the paste lands somewhere the OAuth flow consumes — the hidden
|
||||
// editor's `/login <url>` path is unreachable while the dialog holds
|
||||
// focus (#5339).
|
||||
onManualCodeInput: useManualInput ? () => dialog.showManualInput(MANUAL_LOGIN_PROMPT) : undefined,
|
||||
});
|
||||
this.ctx.session.modelRegistry.refreshInBackground();
|
||||
const block = new TranscriptBlock();
|
||||
@@ -1321,9 +1323,6 @@ export class SelectorController {
|
||||
this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return false;
|
||||
} finally {
|
||||
if (useManualInput) {
|
||||
manualInput.clear(`Manual OAuth input cleared for ${providerId}`);
|
||||
}
|
||||
restoreEditor();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user