@@ -24,6 +24,10 @@ import {
2424 kProviderLabels ,
2525 kProviderOrder ,
2626} from "../lib/model-config.js" ;
27+ import {
28+ isCodexAuthCallbackMessage ,
29+ openCodexAuthWindow ,
30+ } from "../lib/codex-oauth-window.js" ;
2731
2832const html = htm . bind ( h ) ;
2933
@@ -51,6 +55,7 @@ export const Models = () => {
5155 const [ savedModel , setSavedModel ] = useState ( ( ) => kModelsTabCache ?. savedModel || "" ) ;
5256 const [ modelDirty , setModelDirty ] = useState ( false ) ;
5357 const [ savedAiValues , setSavedAiValues ] = useState ( ( ) => kModelsTabCache ?. savedAiValues || { } ) ;
58+ const codexExchangeInFlightRef = useRef ( false ) ;
5459 const codexPopupPollRef = useRef ( null ) ;
5560
5661 const refresh = async ( ) => {
@@ -122,18 +127,43 @@ export const Models = () => {
122127 }
123128 } , [ ] ) ;
124129
130+ const submitCodexAuthInput = async ( input ) => {
131+ const normalizedInput = String ( input || "" ) . trim ( ) ;
132+ if ( ! normalizedInput || codexExchangeInFlightRef . current ) return ;
133+ codexExchangeInFlightRef . current = true ;
134+ setCodexManualInput ( normalizedInput ) ;
135+ setCodexExchanging ( true ) ;
136+ try {
137+ const result = await exchangeCodexOAuth ( normalizedInput ) ;
138+ if ( ! result . ok ) throw new Error ( result . error || "Codex OAuth exchange failed" ) ;
139+ setCodexManualInput ( "" ) ;
140+ showToast ( "Codex connected" , "success" ) ;
141+ setCodexAuthStarted ( false ) ;
142+ setCodexAuthWaiting ( false ) ;
143+ await refreshCodexConnection ( ) ;
144+ } catch ( err ) {
145+ setCodexAuthWaiting ( false ) ;
146+ showToast ( err . message || "Codex OAuth exchange failed" , "error" ) ;
147+ } finally {
148+ codexExchangeInFlightRef . current = false ;
149+ setCodexExchanging ( false ) ;
150+ }
151+ } ;
152+
125153 useEffect ( ( ) => {
126154 const onMessage = async ( e ) => {
127155 if ( e . data ?. codex === "success" ) {
128156 showToast ( "Codex connected" , "success" ) ;
129157 await refreshCodexConnection ( ) ;
158+ } else if ( isCodexAuthCallbackMessage ( e . data ) ) {
159+ await submitCodexAuthInput ( e . data . input ) ;
130160 } else if ( e . data ?. codex === "error" ) {
131161 showToast ( `Codex auth failed: ${ e . data . message || "unknown error" } ` , "error" ) ;
132162 }
133163 } ;
134164 window . addEventListener ( "message" , onMessage ) ;
135165 return ( ) => window . removeEventListener ( "message" , onMessage ) ;
136- } , [ ] ) ;
166+ } , [ submitCodexAuthInput ] ) ;
137167
138168 const setEnvValue = ( key , value ) => {
139169 setEnvVars ( ( prev ) => {
@@ -194,10 +224,9 @@ export const Models = () => {
194224 if ( codexStatus . connected ) return ;
195225 setCodexAuthStarted ( true ) ;
196226 setCodexAuthWaiting ( true ) ;
197- const popup = window . open ( "/auth/codex/start" , "codex-auth" , "popup=yes,width=640,height=780" ) ;
227+ const popup = openCodexAuthWindow ( ) ;
198228 if ( ! popup || popup . closed ) {
199229 setCodexAuthWaiting ( false ) ;
200- window . location . href = "/auth/codex/start" ;
201230 return ;
202231 }
203232 if ( codexPopupPollRef . current ) {
@@ -213,21 +242,7 @@ export const Models = () => {
213242 } ;
214243
215244 const completeCodexAuth = async ( ) => {
216- if ( ! codexManualInput . trim ( ) || codexExchanging ) return ;
217- setCodexExchanging ( true ) ;
218- try {
219- const result = await exchangeCodexOAuth ( codexManualInput . trim ( ) ) ;
220- if ( ! result . ok ) throw new Error ( result . error || "Codex OAuth exchange failed" ) ;
221- setCodexManualInput ( "" ) ;
222- showToast ( "Codex connected" , "success" ) ;
223- setCodexAuthStarted ( false ) ;
224- setCodexAuthWaiting ( false ) ;
225- await refreshCodexConnection ( ) ;
226- } catch ( err ) {
227- showToast ( err . message || "Codex OAuth exchange failed" , "error" ) ;
228- } finally {
229- setCodexExchanging ( false ) ;
230- }
245+ await submitCodexAuthInput ( codexManualInput ) ;
231246 } ;
232247
233248 const handleCodexDisconnect = async ( ) => {
@@ -301,7 +316,23 @@ export const Models = () => {
301316 ? html `< ${ Badge } tone ="success"> Connected</ ${ Badge } > `
302317 : html `< ${ Badge } tone ="warning"> Not connected</ ${ Badge } > ` }
303318 </ div >
304- ${ codexStatus . connected
319+ ${ codexAuthStarted
320+ ? html `
321+ < div class ="flex items-center justify-between gap-2 ">
322+ < p class ="text-xs text-fg-muted ">
323+ ${ codexAuthWaiting
324+ ? "Complete login in the popup. AlphaClaw should finish automatically, but you can paste the redirect URL below if it doesn't."
325+ : "Paste the redirect URL from your browser to finish connecting." }
326+ </ p >
327+ < button
328+ onclick =${ startCodexAuth }
329+ class ="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-secondary shrink-0"
330+ >
331+ Restart
332+ </ button >
333+ </ div >
334+ `
335+ : codexStatus . connected
305336 ? html `
306337 < div class ="flex gap-2 ">
307338 < button
@@ -318,31 +349,15 @@ export const Models = () => {
318349 </ button >
319350 </ div >
320351 `
321- : ! codexAuthStarted
322- ? html `
352+ : html `
323353 < button
324354 onclick =${ startCodexAuth }
325355 class ="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-cyan"
326356 >
327357 Connect Codex OAuth
328358 </ button >
329- `
330- : html `
331- < div class ="flex items-center justify-between gap-2 ">
332- < p class ="text-xs text-fg-muted ">
333- ${ codexAuthWaiting
334- ? "Complete login in the popup, then paste the redirect URL."
335- : "Paste the redirect URL from your browser to finish connecting." }
336- </ p >
337- < button
338- onclick =${ startCodexAuth }
339- class ="text-xs font-medium px-3 py-1.5 rounded-lg ac-btn-secondary shrink-0"
340- >
341- Restart
342- </ button >
343- </ div >
344359 ` }
345- ${ ! codexStatus . connected && codexAuthStarted
360+ ${ codexAuthStarted
346361 ? html `
347362 < p class ="text-xs text-fg-muted ">
348363 After login, copy the full redirect URL (starts with
0 commit comments