Skip to main content

capo_agent/tools/
context.rs

1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3use std::sync::{Arc, Mutex as StdMutex};
4
5use tokio::sync::{mpsc, Mutex};
6
7use crate::permissions::PermissionGate;
8
9#[derive(Debug, Clone)]
10pub enum ToolProgressChunk {
11    Stdout(Vec<u8>),
12    Stderr(Vec<u8>),
13    Status(String),
14}
15
16#[derive(Clone)]
17pub struct SharedCancelToken {
18    inner: Arc<StdMutex<motosan_agent_loop::CancellationToken>>,
19}
20
21impl SharedCancelToken {
22    pub fn new() -> Self {
23        Self {
24            inner: Arc::new(StdMutex::new(motosan_agent_loop::CancellationToken::new())),
25        }
26    }
27
28    pub fn reset(&self) -> motosan_agent_loop::CancellationToken {
29        let mut guard = match self.inner.lock() {
30            Ok(guard) => guard,
31            Err(poisoned) => poisoned.into_inner(),
32        };
33        *guard = motosan_agent_loop::CancellationToken::new();
34        guard.clone()
35    }
36
37    pub fn cancel(&self) {
38        self.current().cancel();
39    }
40
41    pub async fn cancelled(&self) {
42        self.current().cancelled().await;
43    }
44
45    fn current(&self) -> motosan_agent_loop::CancellationToken {
46        match self.inner.lock() {
47            Ok(guard) => guard.clone(),
48            Err(poisoned) => poisoned.into_inner().clone(),
49        }
50    }
51}
52
53impl Default for SharedCancelToken {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59#[derive(Clone)]
60pub struct ToolCtx {
61    pub cwd: PathBuf,
62    pub read_files: Arc<Mutex<HashSet<PathBuf>>>,
63    pub permission_gate: Arc<dyn PermissionGate>,
64    pub progress_tx: mpsc::Sender<ToolProgressChunk>,
65    pub cancel_token: SharedCancelToken,
66}
67
68impl ToolCtx {
69    pub fn new(
70        cwd: impl AsRef<Path>,
71        permission_gate: Arc<dyn PermissionGate>,
72        progress_tx: mpsc::Sender<ToolProgressChunk>,
73    ) -> Self {
74        Self {
75            cwd: cwd.as_ref().to_path_buf(),
76            read_files: Arc::new(Mutex::new(HashSet::new())),
77            permission_gate,
78            progress_tx,
79            cancel_token: SharedCancelToken::new(),
80        }
81    }
82
83    /// Record a file as read for this session.
84    ///
85    /// Callers should store the canonicalized path whenever possible so later
86    /// stale-read guards (`edit` / `write` in future milestones) can do a
87    /// canonical-path lookup without alias mismatches.
88    pub async fn mark_read(&self, path: &Path) {
89        self.read_files.lock().await.insert(path.to_path_buf());
90    }
91
92    pub async fn has_been_read(&self, path: &Path) -> bool {
93        self.read_files.lock().await.contains(path)
94    }
95}