Skip to main content

lash_core/tool_provider/
process.rs

1use std::sync::Arc;
2
3use crate::plugin::PluginError;
4
5#[derive(Clone)]
6pub struct ToolSessionProcessAdmin<'run> {
7    pub(super) session_id: String,
8    pub(super) agent_frame_id: crate::AgentFrameId,
9    pub(super) processes: Arc<dyn crate::ProcessService>,
10    pub(super) process_cancel_ability: Arc<dyn crate::ProcessCancelAbility>,
11    pub(super) effect_controller: crate::runtime::RuntimeEffectControllerHandle<'run>,
12    pub(super) parent_invocation: Option<crate::RuntimeInvocation>,
13    pub(super) tool_call_id: Option<String>,
14    pub(super) execution_env_spec: crate::ProcessExecutionEnvSpec,
15}
16
17impl ToolSessionProcessAdmin<'_> {
18    fn process_scope(&self) -> crate::ProcessOpScope<'_> {
19        crate::ProcessOpScope::new(self.effect_controller.scoped())
20            .with_parent_invocation(self.parent_invocation.clone())
21            .with_agent_frame_id(Some(self.agent_frame_id.clone()))
22    }
23
24    /// Start a process owned by this session and registered to wake it,
25    /// returning its public handle summary. Routes through the same
26    /// [`crate::ProcessService::start_from_request`] path the runtime uses for
27    /// every request-shaped process start, so the child is provider-re-supplied,
28    /// durable, and recoverable through the worker.
29    pub async fn start(
30        &self,
31        mut request: crate::ProcessStartRequest,
32    ) -> Result<crate::ProcessHandleSummary, PluginError> {
33        if request.env_spec.is_none()
34            && matches!(
35                &request.input,
36                crate::ProcessInput::ToolCall { .. } | crate::ProcessInput::Engine { .. }
37            )
38        {
39            request.env_spec = Some(self.execution_env_spec.clone());
40        }
41        self.processes
42            .start_from_request(&self.session_id, request, self.process_scope())
43            .await
44    }
45
46    /// Record the terminal outcome of an Externally-Owned process this session
47    /// owns (ADR 0019). A `shell.start` detach registers its launch as an
48    /// Externally-Owned row and immediately completes it with the launch
49    /// identity — lash never claims it as running. Only Externally-Owned rows
50    /// accept this out-of-band completion.
51    pub async fn complete_external(
52        &self,
53        process_id: &str,
54        await_output: crate::ProcessAwaitOutput,
55    ) -> Result<crate::ProcessRecord, PluginError> {
56        self.processes
57            .complete_external(
58                &self.session_id,
59                process_id,
60                await_output,
61                self.process_scope(),
62            )
63            .await
64    }
65
66    /// Await a process started from this session to its terminal output.
67    pub async fn await_process(
68        &self,
69        process_id: &str,
70    ) -> Result<crate::ProcessAwaitOutput, PluginError> {
71        self.processes
72            .await_process(process_id, self.process_scope())
73            .await
74    }
75
76    pub async fn list_handles(&self) -> Result<Vec<crate::ProcessHandleSummary>, PluginError> {
77        Ok(self
78            .processes
79            .list_visible(
80                &self.session_id,
81                crate::ProcessListMode::Live,
82                self.process_scope(),
83            )
84            .await?
85            .into_iter()
86            .map(crate::ProcessHandleSummary::from)
87            .collect())
88    }
89
90    pub async fn list_all_handles(&self) -> Result<Vec<crate::ProcessHandleSummary>, PluginError> {
91        Ok(self
92            .processes
93            .list_visible(
94                &self.session_id,
95                crate::ProcessListMode::All,
96                self.process_scope(),
97            )
98            .await?
99            .into_iter()
100            .map(crate::ProcessHandleSummary::from)
101            .collect())
102    }
103
104    pub async fn list_handles_filtered(
105        &self,
106        filter: &crate::ProcessListFilter,
107    ) -> Result<Vec<crate::ProcessHandleSummary>, PluginError> {
108        Ok(self
109            .processes
110            .list_visible(&self.session_id, filter.list_mode(), self.process_scope())
111            .await?
112            .into_iter()
113            .filter(|entry| filter.matches_entry(entry))
114            .map(crate::ProcessHandleSummary::from)
115            .collect())
116    }
117
118    pub async fn validate_handles(&self, handle_ids: &[String]) -> Result<(), PluginError> {
119        self.processes
120            .validate_visible(&self.session_id, handle_ids, self.process_scope())
121            .await
122    }
123
124    pub async fn cancel(
125        &self,
126        process_id: &str,
127    ) -> Result<crate::ProcessCancelSummary, PluginError> {
128        let request = crate::ProcessCancelRequest::new(
129            &self.session_id,
130            process_id,
131            self.process_scope(),
132            crate::ProcessCancelSource::Tool,
133        )
134        .with_reason("requested by tool");
135        self.process_cancel_ability
136            .cancel_summary(self.processes.as_ref(), request)
137            .await
138    }
139
140    pub async fn signal(
141        &self,
142        process_id: &str,
143        signal_name: &str,
144        payload: serde_json::Value,
145    ) -> Result<crate::ProcessEvent, PluginError> {
146        let signal_id = self
147            .tool_call_id
148            .clone()
149            .unwrap_or_else(|| format!("adhoc-{}", uuid::Uuid::new_v4()));
150        self.processes
151            .signal(
152                &self.session_id,
153                process_id,
154                signal_name.to_string(),
155                signal_id,
156                payload,
157                self.process_scope(),
158            )
159            .await
160    }
161
162    pub async fn cancel_all(&self) -> Result<Vec<crate::ProcessCancelSummary>, PluginError> {
163        self.process_cancel_ability
164            .cancel_all_visible(
165                self.processes.as_ref(),
166                crate::ProcessCancelAllRequest::new(
167                    &self.session_id,
168                    self.process_scope(),
169                    crate::ProcessCancelSource::Tool,
170                )
171                .with_reason("requested by tool"),
172            )
173            .await
174    }
175
176    pub async fn transfer_handles(
177        &self,
178        to_session_id: &str,
179        process_ids: Vec<String>,
180    ) -> Result<(), PluginError> {
181        self.processes
182            .transfer(
183                &self.session_id,
184                to_session_id,
185                process_ids,
186                self.process_scope(),
187            )
188            .await
189    }
190
191    pub async fn transfer_handles_to_frame(
192        &self,
193        to_agent_frame_id: &str,
194        process_ids: Vec<String>,
195    ) -> Result<(), PluginError> {
196        self.processes
197            .transfer(
198                &self.session_id,
199                &self.session_id,
200                process_ids,
201                self.process_scope()
202                    .with_target_agent_frame_id(Some(to_agent_frame_id.to_string())),
203            )
204            .await
205    }
206
207    pub async fn cancel_unreferenced_handles(
208        &self,
209        keep_process_ids: Vec<String>,
210    ) -> Result<Vec<crate::ProcessCancelSummary>, PluginError> {
211        Ok(self
212            .processes
213            .cancel_unreferenced(&self.session_id, keep_process_ids, self.process_scope())
214            .await?
215            .into_iter()
216            .map(crate::ProcessCancelSummary::from_record)
217            .collect())
218    }
219}