@@ -74,7 +74,15 @@ export default function AcpPageInclude() {
7474 setFormStatus ( "Connecting..." ) ;
7575 await ensureReadyForUrl ( url ) ;
7676 setFormStatus ( "Starting session..." ) ;
77- await client . newSession ( nextCwd || undefined ) ;
77+
78+ try {
79+ await client . newSession ( nextCwd || undefined ) ;
80+ } catch ( sessionErr ) {
81+ if ( ! ACPClient . isAuthRequiredError ( sessionErr ) ) throw sessionErr ;
82+ await handleAuthentication ( ) ;
83+ await client . newSession ( nextCwd || undefined ) ;
84+ }
85+
7886 currentSessionUrl = url ;
7987 switchToChat ( client . agentName ) ;
8088 saveCurrentSessionHistory ( ) ;
@@ -85,6 +93,120 @@ export default function AcpPageInclude() {
8593 }
8694 }
8795
96+ async function handleAuthentication ( ) {
97+ const methods = client . authMethods ;
98+ if ( ! methods . length ) {
99+ throw new Error (
100+ "Agent requires authentication but did not advertise any auth methods." ,
101+ ) ;
102+ }
103+
104+ let selectedMethod = methods [ 0 ] ;
105+
106+ if ( methods . length > 1 ) {
107+ setFormStatus ( "Authentication required — choose a method" ) ;
108+ const picked = await select (
109+ "Authentication Required" ,
110+ methods . map ( ( m ) => ( {
111+ value : m . id ,
112+ text : m . name + ( m . description ? ` — ${ m . description } ` : "" ) ,
113+ icon : "vpn_key" ,
114+ } ) ) ,
115+ { textTransform : false } ,
116+ ) ;
117+ if ( ! picked ) throw new Error ( "Authentication cancelled" ) ;
118+ selectedMethod = methods . find ( ( m ) => m . id === picked ) || methods [ 0 ] ;
119+ }
120+
121+ let browserOpened = false ;
122+ const maybeOpenAuthUrl = ( value ) => {
123+ const url = extractExternalUrl ( value ) ;
124+ if ( ! url || browserOpened ) return false ;
125+ browserOpened = true ;
126+ system . openInBrowser ( url ) ;
127+ setFormStatus ( "Complete sign-in in your browser…" ) ;
128+ return true ;
129+ } ;
130+
131+ const handleExtNotification = ( event ) => {
132+ maybeOpenAuthUrl ( event ) ;
133+ } ;
134+ const handleExtRequest = ( event ) => {
135+ maybeOpenAuthUrl ( event ) ;
136+ } ;
137+ const handleAuthExtRequest = async ( method , params = { } ) => {
138+ const didOpen = maybeOpenAuthUrl ( { method, params } ) ;
139+ if ( ! didOpen ) {
140+ throw RequestError . methodNotFound ( method ) ;
141+ }
142+ return {
143+ ok : true ,
144+ handled : true ,
145+ opened : true ,
146+ } ;
147+ } ;
148+
149+ const { alpineRoot } = getTerminalPaths ( ) ;
150+ const urlTempPath = `file://${ alpineRoot } /tmp/.acode_open_url` ;
151+ try {
152+ await fsOperation ( urlTempPath ) . delete ( ) ;
153+ } catch {
154+ /* ignore */
155+ }
156+
157+ setFormStatus ( `Authenticating via ${ selectedMethod . name } …` ) ;
158+
159+ client . setExtensionRequestHandler ( handleAuthExtRequest ) ;
160+ client . on ( "ext_request" , handleExtRequest ) ;
161+ client . on ( "ext_notification" , handleExtNotification ) ;
162+ const stopPolling = startAuthUrlPolling ( urlTempPath ) ;
163+ try {
164+ const response = await client . authenticate ( selectedMethod . id ) ;
165+ maybeOpenAuthUrl ( response ) ;
166+ } finally {
167+ stopPolling ( ) ;
168+ client . setExtensionRequestHandler ( null ) ;
169+ client . off ( "ext_request" , handleExtRequest ) ;
170+ client . off ( "ext_notification" , handleExtNotification ) ;
171+ }
172+
173+ setFormStatus ( "Authenticated — starting session…" ) ;
174+ }
175+
176+ function startAuthUrlPolling ( urlFilePath ) {
177+ let stopped = false ;
178+ let opened = false ;
179+
180+ const poll = async ( ) => {
181+ while ( ! stopped ) {
182+ await new Promise ( ( resolve ) => setTimeout ( resolve , 400 ) ) ;
183+ if ( stopped ) break ;
184+ try {
185+ const raw = await fsOperation ( urlFilePath ) . readFile ( "utf8" ) ;
186+ const url = String ( raw || "" ) . trim ( ) ;
187+ if ( url && ! opened ) {
188+ opened = true ;
189+ system . openInBrowser ( url ) ;
190+ setFormStatus ( "Complete sign-in in your browser…" ) ;
191+ try {
192+ await fsOperation ( urlFilePath ) . delete ( ) ;
193+ } catch {
194+ /* ignore */
195+ }
196+ }
197+ } catch {
198+ /* file doesn't exist yet */
199+ }
200+ }
201+ } ;
202+
203+ void poll ( ) ;
204+
205+ return ( ) => {
206+ stopped = true ;
207+ } ;
208+ }
209+
88210 function getTerminalPaths ( ) {
89211 const packageName = window . BuildInfo ?. packageName || "com.foxdebug.acode" ;
90212 const dataDir = `/data/user/0/${ packageName } ` ;
@@ -95,6 +217,58 @@ export default function AcpPageInclude() {
95217 } ;
96218 }
97219
220+ function extractExternalUrl ( value , visited = new Set ( ) ) {
221+ if ( typeof value === "string" ) {
222+ const trimmed = value . trim ( ) ;
223+ return / ^ ( h t t p s ? | f t p s ? | m a i l t o | t e l | s m s | g e o ) : / i. test ( trimmed )
224+ ? trimmed
225+ : "" ;
226+ }
227+
228+ if ( ! value || typeof value !== "object" ) return "" ;
229+ if ( visited . has ( value ) ) return "" ;
230+ visited . add ( value ) ;
231+
232+ if ( Array . isArray ( value ) ) {
233+ for ( const entry of value ) {
234+ const nestedUrl = extractExternalUrl ( entry , visited ) ;
235+ if ( nestedUrl ) return nestedUrl ;
236+ }
237+ return "" ;
238+ }
239+
240+ const prioritizedKeys = [
241+ "url" ,
242+ "uri" ,
243+ "href" ,
244+ "openUrl" ,
245+ "open_url" ,
246+ "browserUrl" ,
247+ "browser_url" ,
248+ "verificationUri" ,
249+ "verification_uri" ,
250+ "verificationUrl" ,
251+ "verification_url" ,
252+ "authorizationUrl" ,
253+ "authorization_url" ,
254+ "authorizeUrl" ,
255+ "authorize_url" ,
256+ ] ;
257+
258+ for ( const key of prioritizedKeys ) {
259+ if ( ! ( key in value ) ) continue ;
260+ const nestedUrl = extractExternalUrl ( value [ key ] , visited ) ;
261+ if ( nestedUrl ) return nestedUrl ;
262+ }
263+
264+ for ( const nestedValue of Object . values ( value ) ) {
265+ const nestedUrl = extractExternalUrl ( nestedValue , visited ) ;
266+ if ( nestedUrl ) return nestedUrl ;
267+ }
268+
269+ return "" ;
270+ }
271+
98272 function normalizePathInput ( value = "" ) {
99273 return String ( value || "" )
100274 . trim ( )
@@ -1455,7 +1629,13 @@ export default function AcpPageInclude() {
14551629 switchToChat ( entry . agentName || client . agentName ) ;
14561630 updateStatusDot ( "connecting" ) ;
14571631
1458- await client . loadSession ( entry . sessionId , cwd ) ;
1632+ try {
1633+ await client . loadSession ( entry . sessionId , cwd ) ;
1634+ } catch ( loadErr ) {
1635+ if ( ! ACPClient . isAuthRequiredError ( loadErr ) ) throw loadErr ;
1636+ await handleAuthentication ( ) ;
1637+ await client . loadSession ( entry . sessionId , cwd ) ;
1638+ }
14591639 client . session ?. finishAgentTurn ( ) ;
14601640 currentSessionUrl = entry . url ;
14611641 setChatAgentName ( entry . agentName || client . agentName ) ;
0 commit comments