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