| |
| |
|
|
| use std::future::Future; |
| use std::sync::Arc; |
|
|
| use codex_protocol::error::CodexErr; |
| use codex_protocol::error::Result as CodexResult; |
| use tokio::sync::oneshot; |
| use tokio_util::sync::CancellationToken; |
| use tokio_util::task::TaskTracker; |
|
|
| use super::NewThread; |
| use super::StartThreadOptions; |
| use super::ThreadManager; |
| use crate::session::startup::SessionStartup; |
|
|
| impl ThreadManager { |
| |
| |
| |
| |
| |
| |
| |
| pub async fn start_thread_until( |
| self: &Arc<Self>, |
| options: StartThreadOptions, |
| until: impl Future<Output = ()> + Send + 'static, |
| tasks: &TaskTracker, |
| ) -> CodexResult<NewThread> { |
| if matches!( |
| options.initial_history, |
| codex_history::InitialHistory::Resumed(_) |
| ) { |
| return Err(CodexErr::InvalidRequest( |
| "owned thread startup requires new or forked history".to_owned(), |
| )); |
| } |
| if options |
| .thread_extension_init |
| .get::<codex_extension_api::SessionIsolation>() |
| .as_deref() |
| != Some(&codex_extension_api::SessionIsolation::Isolated) |
| { |
| return Err(CodexErr::InvalidRequest( |
| "owned thread startup requires explicit session isolation".to_owned(), |
| )); |
| } |
| let task = tasks.token(); |
| let manager = Arc::clone(self); |
| let state = Arc::downgrade(&self.state); |
| let (sender, receiver) = oneshot::channel(); |
| let abandoned = CancellationToken::new(); |
| let handoff = abandoned.clone().drop_guard(); |
| tokio::spawn(async move { |
| let _task = task; |
| let startup = Arc::new(SessionStartup::default()); |
| tokio::pin!(until); |
| |
| let result = { |
| let start = manager.start_thread_inner( |
| options, |
| None, |
| Some(Arc::clone(&startup)), |
| ); |
| tokio::select! { |
| biased; |
| _ = &mut until => Err(CodexErr::TurnAborted), |
| _ = abandoned.cancelled() => Err(CodexErr::TurnAborted), |
| result = start => result, |
| } |
| }; |
| |
| drop(manager); |
| match result { |
| Ok(thread) => { |
| let running = Arc::clone(&thread.thread); |
| if sender.send(Ok(thread)).is_ok() { |
| tokio::select! { |
| _ = abandoned.cancelled() => {} |
| _ = &mut until => {} |
| _ = running.wait_until_terminated() => {} |
| } |
| } |
| } |
| Err(error) => { |
| let _ = sender.send(Err(error)); |
| } |
| } |
| startup.cleanup().await; |
| if let Some(state) = state.upgrade() |
| && let Some(session) = startup.session.get() |
| { |
| let mut threads = state.threads.write().await; |
| let thread_id = session.thread_id(); |
| if threads |
| .get(&thread_id) |
| .is_some_and(|thread| Arc::ptr_eq(&thread.session, session)) |
| { |
| threads.remove(&thread_id); |
| } |
| } |
| }); |
| let thread = receiver.await.map_err(|_| CodexErr::InternalAgentDied)??; |
| handoff.disarm(); |
| Ok(thread) |
| } |
| } |
|
|