Skip to main content

muster/domain/process/
entity.rs

1use std::path::PathBuf;
2
3use getset::Getters;
4use typed_builder::TypedBuilder;
5
6use crate::domain::{
7    agent_session::AgentSessionId,
8    process::{
9        ActivityState, AgentTool, ProcessKind, ProcessOrigin, ProcessState, RestartPolicy,
10        StopPolicy,
11    },
12    value::{CommandLine, Description, PaneId, ProcessName},
13};
14
15/// A configured process managed by the workspace: an agent, terminal, or command.
16#[derive(Clone, Getters, TypedBuilder)]
17#[getset(get = "pub")]
18pub struct Process {
19    /// Stable identity for this process and its pane.
20    id: PaneId,
21    /// Display name shown in the sidebar.
22    name: ProcessName,
23    /// Section the process belongs to.
24    kind: ProcessKind,
25    /// Agent preset used for provider-aware activity detection.
26    #[builder(default)]
27    agent_tool: Option<AgentTool>,
28    /// Durable identity when this process is a first-class agent session.
29    #[builder(default)]
30    agent_session_id: Option<AgentSessionId>,
31    /// Whether configuration or runtime session history owns the process.
32    #[builder(default)]
33    origin: ProcessOrigin,
34    /// Command used to launch it, or the user's login shell when absent.
35    #[builder(default)]
36    command: Option<CommandLine>,
37    /// Working directory to launch in; inherits the workspace cwd when absent.
38    #[builder(default)]
39    working_dir: Option<PathBuf>,
40    /// Optional secondary line under the name in the sidebar.
41    #[builder(default)]
42    description: Option<Description>,
43    /// Current lifecycle state.
44    #[builder(default)]
45    state: ProcessState,
46    /// Restart policy governing what happens when the child exits.
47    #[builder(default)]
48    restart: RestartPolicy,
49    /// Optional graceful shutdown policy, valid only for command processes.
50    #[builder(default)]
51    stop: Option<StopPolicy>,
52    /// Whether this process launches automatically when its workspace loads.
53    #[builder(default = true)]
54    autostart: bool,
55    /// What the process appears to be doing, inferred from its terminal signals.
56    #[builder(default)]
57    activity: ActivityState,
58}
59
60impl Process {
61    /// Transitions the process to a new lifecycle state.
62    pub fn set_state(&mut self, state: ProcessState) {
63        self.state = state;
64    }
65
66    /// Sets whether this process auto-starts when its workspace loads.
67    pub fn set_autostart(&mut self, autostart: bool) {
68        self.autostart = autostart;
69    }
70
71    /// Updates the inferred activity of the process.
72    pub fn set_activity(&mut self, activity: ActivityState) {
73        self.activity = activity;
74    }
75
76    /// The effective graceful shutdown policy. Commands use the domain default
77    /// when their config omits an override; other process kinds have no policy.
78    pub fn effective_stop_policy(&self) -> Option<StopPolicy> {
79        if self.kind == ProcessKind::Command {
80            Some(self.stop.clone().unwrap_or_default())
81        } else {
82            None
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn builds_with_defaults_for_optional_fields() {
93        let process = Process::builder()
94            .id(PaneId::new(1))
95            .name(ProcessName::try_new("Claude Code").unwrap())
96            .kind(ProcessKind::Agent)
97            .command(Some(CommandLine::try_new("claude").unwrap()))
98            .build();
99
100        assert_eq!(*process.kind(), ProcessKind::Agent);
101        assert_eq!(*process.origin(), ProcessOrigin::Configured);
102        assert_eq!(*process.state(), ProcessState::Pending);
103        assert!(process.working_dir().is_none());
104        assert!(process.description().is_none());
105    }
106
107    #[test]
108    fn command_defaults_to_none() {
109        let process = Process::builder()
110            .id(PaneId::new(1))
111            .name(ProcessName::try_new("shell").unwrap())
112            .kind(ProcessKind::Terminal)
113            .build();
114        assert!(process.command().is_none());
115    }
116}