@@ -1218,6 +1218,101 @@ impl Agent {
12181218 }
12191219}
12201220
1221+ // ---------------------------------------------------------------------------
1222+ // AgentHandle — thread-safe wrapper for mobile and server consumers
1223+ // ---------------------------------------------------------------------------
1224+
1225+ /// Thread-safe handle to an Agent. Mobile and server code use this instead of
1226+ /// Agent directly. Wraps in `Arc<tokio::sync::Mutex<Agent>>` so it can be
1227+ /// shared across threads and held across `.await` points.
1228+ #[ derive( Clone ) ]
1229+ pub struct AgentHandle {
1230+ inner : Arc < tokio:: sync:: Mutex < Agent > > ,
1231+ }
1232+
1233+ // Compile-time guarantee that AgentHandle is safe to share across threads.
1234+ const _: ( ) = {
1235+ fn assert_send_sync < T : Send + Sync > ( ) { }
1236+ fn check ( ) {
1237+ assert_send_sync :: < AgentHandle > ( ) ;
1238+ }
1239+ } ;
1240+
1241+ impl AgentHandle {
1242+ /// Create a new handle wrapping an existing Agent.
1243+ pub fn new ( agent : Agent ) -> Self {
1244+ Self {
1245+ inner : Arc :: new ( tokio:: sync:: Mutex :: new ( agent) ) ,
1246+ }
1247+ }
1248+
1249+ /// Send a chat message and return the full response text.
1250+ pub async fn chat ( & self , message : & str ) -> Result < String > {
1251+ let mut agent = self . inner . lock ( ) . await ;
1252+ agent. chat ( message) . await
1253+ }
1254+
1255+ /// Start a new session.
1256+ pub async fn new_session ( & self ) -> Result < ( ) > {
1257+ let mut agent = self . inner . lock ( ) . await ;
1258+ agent. new_session ( ) . await
1259+ }
1260+
1261+ /// Search memory files.
1262+ pub async fn memory_search ( & self , query : & str , max_results : usize ) -> Result < Vec < MemoryChunk > > {
1263+ let agent = self . inner . lock ( ) . await ;
1264+ agent. search_memory ( query) . await . map ( |mut results| {
1265+ results. truncate ( max_results) ;
1266+ results
1267+ } )
1268+ }
1269+
1270+ /// Read a memory file by name.
1271+ pub async fn memory_get ( & self , filename : & str ) -> Result < String > {
1272+ let agent = self . inner . lock ( ) . await ;
1273+ let workspace = agent. memory . workspace ( ) ;
1274+ let path = workspace. join ( filename) ;
1275+ std:: fs:: read_to_string ( & path)
1276+ . map_err ( |e| anyhow:: anyhow!( "Failed to read {}: {}" , filename, e) )
1277+ }
1278+
1279+ /// Get the current model name.
1280+ pub async fn model ( & self ) -> String {
1281+ let agent = self . inner . lock ( ) . await ;
1282+ agent. model ( ) . to_string ( )
1283+ }
1284+
1285+ /// Switch to a different model.
1286+ pub async fn set_model ( & self , model : & str ) -> Result < ( ) > {
1287+ let mut agent = self . inner . lock ( ) . await ;
1288+ agent. set_model ( model)
1289+ }
1290+
1291+ /// Get context window usage: (used, usable, total).
1292+ pub async fn context_usage ( & self ) -> ( usize , usize , usize ) {
1293+ let agent = self . inner . lock ( ) . await ;
1294+ agent. context_usage ( )
1295+ }
1296+
1297+ /// Compact the session history.
1298+ pub async fn compact_session ( & self ) -> Result < ( usize , usize ) > {
1299+ let mut agent = self . inner . lock ( ) . await ;
1300+ agent. compact_session ( ) . await
1301+ }
1302+
1303+ /// Clear session history.
1304+ pub async fn clear_session ( & self ) {
1305+ let mut agent = self . inner . lock ( ) . await ;
1306+ agent. clear_session ( ) ;
1307+ }
1308+
1309+ /// Export session as markdown.
1310+ pub async fn export_markdown ( & self ) -> String {
1311+ let agent = self . inner . lock ( ) . await ;
1312+ agent. export_markdown ( )
1313+ }
1314+ }
1315+
12211316/// Welcome message shown on first run (brand new workspace)
12221317const FIRST_RUN_WELCOME : & str = r#"# Welcome to LocalGPT
12231318
0 commit comments