agent_engine/daemon/
pin.rs1use std::sync::Arc;
11use std::time::Duration;
12
13use super::DaemonState;
14use crate::session::wire::{DaemonFrame, RefuseReason};
15use crate::session::{SessionCommand, SessionId, SessionLifecycle, TransportError};
16
17pub const PIN_BUDGET: Duration = Duration::from_secs(2);
19pub const PIN_UNPARK_BUDGET: Duration = Duration::from_secs(10);
22const PIN_POLL: Duration = Duration::from_millis(20);
24
25pub async fn pin_session(state: &Arc<DaemonState>, id: &SessionId, on: bool) -> DaemonFrame {
28 if state.reloading.load(std::sync::atomic::Ordering::SeqCst) {
31 return DaemonFrame::Refused { reason: RefuseReason::Busy, message: "daemon reloading; retry in a moment".into() };
32 }
33 let live = state.attach(id);
36 if live.as_ref().is_none_or(|h| h.meta().locked_by.is_some() && !on) {
37 match super::pins::pin_offline(state, id.as_str(), on).await {
38 Some(Ok(())) => return DaemonFrame::Pinned { session_id: id.clone(), on },
39 Some(Err(message)) => return DaemonFrame::Error { session_id: Some(id.clone()), message },
40 None => {}
41 }
42 }
43 let Some(handle) = live else {
44 return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("no live session {id}") };
45 };
46 let sid = handle.id.clone();
47 if let Some(holder) = handle.meta().locked_by.clone() {
48 return DaemonFrame::Refused {
49 reason: RefuseReason::Busy,
50 message: format!(
54 "session {sid} is live in another process ({holder}), not in this daemon, so there is nothing \
55 to pin here. Quit it there, then load it into the daemon with `synaps --attach --continue {sid}` \
56 and pin it with /pin or `synaps daemon pin {sid}`"
57 ),
58 };
59 }
60
61 let budget = if on && handle.lifecycle() == SessionLifecycle::Parked { PIN_UNPARK_BUDGET } else { PIN_BUDGET };
62 let deadline = tokio::time::Instant::now() + budget;
63 loop {
64 match handle.send(SessionCommand::KeepWarm { on }).await {
65 Ok(()) => break,
66 Err(TransportError::Backpressure) if tokio::time::Instant::now() < deadline => {
67 tokio::time::sleep(PIN_POLL).await;
68 }
69 Err(TransportError::Backpressure) => {
70 return DaemonFrame::Error {
71 session_id: Some(sid),
72 message: "pin: the session's command queue stayed full; retry".into(),
73 };
74 }
75 Err(_) => {
76 return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("session {sid} has ended") };
77 }
78 }
79 }
80 while handle.pinned() != on {
81 if !handle.is_alive() {
82 return DaemonFrame::Refused { reason: RefuseReason::UnknownSession, message: format!("session {sid} has ended") };
83 }
84 if tokio::time::Instant::now() >= deadline {
85 return DaemonFrame::Error {
86 session_id: Some(sid),
87 message: format!("pin: the session did not apply it within {} s", budget.as_secs()),
88 };
89 }
90 tokio::time::sleep(PIN_POLL).await;
91 }
92 DaemonFrame::Pinned { session_id: sid, on }
93}