@@ -169,8 +169,12 @@ struct DrainedEvents {
169169/// Compute a hash of the [`LaunchOptions`] fields that require a browser
170170/// relaunch when changed (baked into the Chrome process at startup).
171171///
172- /// Fields NOT hashed (adjustable at runtime via CDP without relaunch):
173- /// ignore_https_errors, color_scheme, download_path, storage_state
172+ /// Fields NOT hashed:
173+ /// ignore_https_errors, color_scheme, download_path
174+ ///
175+ /// `storage_state` is handled separately in `handle_launch()`: explicit
176+ /// `storageState` launches always require a clean local browser so the loaded
177+ /// state replaces the prior session instead of merging into it.
174178fn launch_hash ( opts : & LaunchOptions ) -> u64 {
175179 use std:: collections:: hash_map:: DefaultHasher ;
176180 use std:: hash:: { Hash , Hasher } ;
@@ -1504,6 +1508,9 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
15041508 }
15051509 let engine = env:: var ( "AGENT_BROWSER_ENGINE" ) . ok ( ) ;
15061510
1511+ // Extract storage_state before options is moved into BrowserManager::launch.
1512+ let storage_state_path = options. storage_state . clone ( ) ;
1513+
15071514 // Store proxy credentials for Fetch.authRequired handling
15081515 let has_proxy_auth = options. proxy_username . is_some ( ) ;
15091516 if has_proxy_auth {
@@ -1527,6 +1534,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
15271534 state. start_dialog_handler ( ) ;
15281535 state. update_stream_client ( ) . await ;
15291536 try_auto_restore_state ( state) . await ;
1537+ try_load_storage_state ( state, & storage_state_path) . await ;
15301538 return Ok ( ( ) ) ;
15311539 }
15321540
@@ -1538,6 +1546,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
15381546 state. start_dialog_handler ( ) ;
15391547 state. update_stream_client ( ) . await ;
15401548 try_auto_restore_state ( state) . await ;
1549+ try_load_storage_state ( state, & storage_state_path) . await ;
15411550 return Ok ( ( ) ) ;
15421551 }
15431552
@@ -1572,6 +1581,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
15721581 state. update_stream_client ( ) . await ;
15731582 write_provider_file ( & state. session_id , & p) ;
15741583 try_auto_restore_state ( state) . await ;
1584+ try_load_storage_state ( state, & storage_state_path) . await ;
15751585 return Ok ( ( ) ) ;
15761586 }
15771587 Err ( e) => {
@@ -1604,6 +1614,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
16041614 }
16051615
16061616 try_auto_restore_state ( state) . await ;
1617+ try_load_storage_state ( state, & storage_state_path) . await ;
16071618 Ok ( ( ) )
16081619}
16091620
@@ -1665,6 +1676,64 @@ async fn try_auto_restore_state(state: &mut DaemonState) {
16651676 }
16661677}
16671678
1679+ /// Load storage state if a path is configured.
1680+ ///
1681+ /// Explicit launch should surface this error. Best-effort callers can ignore
1682+ /// the returned `Result` and keep their previous behavior.
1683+ async fn load_storage_state ( state : & DaemonState , path : & Option < String > ) -> Result < ( ) , String > {
1684+ if let Some ( ref path) = path {
1685+ if let Some ( ref mgr) = state. browser {
1686+ if let Ok ( session_id) = mgr. active_session_id ( ) {
1687+ state:: load_state ( & mgr. client , session_id, path) . await ?;
1688+ }
1689+ }
1690+ }
1691+
1692+ Ok ( ( ) )
1693+ }
1694+
1695+ async fn rollback_failed_launch ( state : & mut DaemonState ) -> Result < ( ) , String > {
1696+ let close_error = if let Some ( mut mgr) = state. browser . take ( ) {
1697+ mgr. close ( ) . await . err ( )
1698+ } else {
1699+ None
1700+ } ;
1701+
1702+ state. launch_hash = None ;
1703+ state. screencasting = false ;
1704+ state. reset_input_state ( ) ;
1705+ state. ref_map . clear ( ) ;
1706+ state. update_stream_client ( ) . await ;
1707+
1708+ if let Some ( err) = close_error {
1709+ return Err ( err) ;
1710+ }
1711+
1712+ Ok ( ( ) )
1713+ }
1714+
1715+ async fn load_storage_state_or_rollback (
1716+ state : & mut DaemonState ,
1717+ path : & Option < String > ,
1718+ ) -> Result < ( ) , String > {
1719+ if let Err ( err) = load_storage_state ( state, path) . await {
1720+ if let Err ( close_err) = rollback_failed_launch ( state) . await {
1721+ return Err ( format ! (
1722+ "{} (also failed to roll back browser after launch: {})" ,
1723+ err, close_err
1724+ ) ) ;
1725+ }
1726+ return Err ( err) ;
1727+ }
1728+
1729+ Ok ( ( ) )
1730+ }
1731+
1732+ /// Load storage state from AGENT_BROWSER_STATE if set.
1733+ async fn try_load_storage_state ( state : & DaemonState , path : & Option < String > ) {
1734+ let _ = load_storage_state ( state, path) . await ;
1735+ }
1736+
16681737// ---------------------------------------------------------------------------
16691738// Phase 1 handlers
16701739// ---------------------------------------------------------------------------
@@ -1688,6 +1757,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
16881757 . collect ( )
16891758 } ) ;
16901759 let storage_state = cmd. get ( "storageState" ) . and_then ( |v| v. as_str ( ) ) ;
1760+ let storage_state_owned = storage_state. map ( |s| s. to_string ( ) ) ;
16911761
16921762 let launch_options = LaunchOptions {
16931763 headless,
@@ -1768,8 +1838,10 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
17681838 let is_external = cdp_url. is_some ( ) || cdp_port. is_some ( ) || auto_connect;
17691839 let was_external = mgr. is_cdp_connection ( ) ;
17701840 let hash_changed = !is_external && state. launch_hash != Some ( new_hash) ;
1841+ let storage_state_requires_clean_launch = storage_state_owned. is_some ( ) && !is_external;
17711842 is_external != was_external
17721843 || hash_changed
1844+ || storage_state_requires_clean_launch
17731845 || mgr. has_process_exited ( )
17741846 || !mgr. is_connection_alive ( ) . await
17751847 } else {
@@ -1786,6 +1858,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
17861858 state. update_stream_client ( ) . await ;
17871859 }
17881860 } else {
1861+ load_storage_state ( state, & storage_state_owned) . await ?;
17891862 return Ok ( json ! ( { "launched" : true , "reused" : true } ) ) ;
17901863 }
17911864 state. ref_map . clear ( ) ;
@@ -1807,6 +1880,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
18071880 state. start_fetch_handler ( ) ;
18081881 state. start_dialog_handler ( ) ;
18091882 state. update_stream_client ( ) . await ;
1883+ load_storage_state_or_rollback ( state, & storage_state_owned) . await ?;
18101884 return Ok ( json ! ( { "launched" : true } ) ) ;
18111885 }
18121886
@@ -1817,6 +1891,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
18171891 state. start_fetch_handler ( ) ;
18181892 state. start_dialog_handler ( ) ;
18191893 state. update_stream_client ( ) . await ;
1894+ load_storage_state_or_rollback ( state, & storage_state_owned) . await ?;
18201895 return Ok ( json ! ( { "launched" : true } ) ) ;
18211896 }
18221897
@@ -1827,6 +1902,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
18271902 state. start_fetch_handler ( ) ;
18281903 state. start_dialog_handler ( ) ;
18291904 state. update_stream_client ( ) . await ;
1905+ load_storage_state_or_rollback ( state, & storage_state_owned) . await ?;
18301906 return Ok ( json ! ( { "launched" : true } ) ) ;
18311907 }
18321908
@@ -1863,6 +1939,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
18631939 state. start_dialog_handler ( ) ;
18641940 state. update_stream_client ( ) . await ;
18651941 write_provider_file ( & state. session_id , provider) ;
1942+ load_storage_state_or_rollback ( state, & storage_state_owned) . await ?;
18661943
18671944 if let Some ( info) = providers:: get_agentcore_info ( ) {
18681945 return Ok ( json ! ( {
@@ -1955,6 +2032,11 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
19552032 }
19562033 }
19572034
2035+ // Load storage state only after Fetch interception is active so replayed
2036+ // origin navigations go through the same domain and proxy handling as
2037+ // normal browser traffic.
2038+ load_storage_state_or_rollback ( state, & storage_state_owned) . await ?;
2039+
19582040 Ok ( json ! ( { "launched" : true } ) )
19592041}
19602042
0 commit comments