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