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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! Supervisor configuration: poll cadence, auto-resume policy, idle classification.
//!
//! Why: the unattended supervisor must run with no live caller for hours, so its
//! behavior has to be driven entirely by *seed configuration* an operator sets
//! once (env vars / a struct) rather than by interactive prompts. Centralizing
//! the knobs in one struct keeps the env-var contract (`TRUSTY_MPM_AUTO_RESUME`,
//! `TRUSTY_MPM_SUPERVISOR_INTERVAL`, …) auditable and gives the loop a single
//! immutable config value to read each tick.
//! What: defines [`SupervisorConfig`] with the poll interval, the auto-resume
//! gate, the idle-classification toggle, and the metrics bind address; plus
//! [`SupervisorConfig::from_env`] which reads the documented env vars with safe
//! defaults.
//! Test: `config_defaults`, `auto_resume_env_parsing`, `interval_env_parsing`,
//! `classify_idle_env_parsing` in `super::tests`.
use SocketAddr;
use Duration;
/// Environment variable that gates auto-resume of `stopped` sessions.
///
/// Why: auto-resume is the supervisor's most consequential action — it brings a
/// runtime back without a human present — so it MUST be opt-in via an explicit
/// env var, matching the existing reconcile-on-boot gate in `DaemonState`.
/// What: the canonical name an operator sets to `1` / `true` to enable resume.
/// Test: `auto_resume_env_parsing`.
pub const ENV_AUTO_RESUME: &str = "TRUSTY_MPM_AUTO_RESUME";
/// Environment variable that overrides the poll interval (in seconds).
///
/// Why: overnight fleets want a slow cadence (cheap); active debugging wants a
/// fast one. An env override lets operators tune without recompiling.
/// What: parsed as `u64` seconds; invalid / absent values fall back to the default.
/// Test: `interval_env_parsing`.
pub const ENV_INTERVAL_SECS: &str = "TRUSTY_MPM_SUPERVISOR_INTERVAL";
/// Environment variable that selects the LLM model for idle classification.
///
/// Why: idle-session activity classification calls an LLM through OpenRouter;
/// the model is operator-tunable (cost vs. accuracy) and must be read from one
/// named constant so the env contract is auditable alongside the other `ENV_*`
/// knobs rather than buried as a string literal at the read site.
/// What: the variable an operator sets to override the classification model;
/// when absent the supervisor falls back to [`DEFAULT_LLM_MODEL`].
/// Test: `default_llm_model_is_documented` asserts the constant + default pair.
pub const ENV_LLM_MODEL: &str = "TRUSTY_LLM_MODEL";
/// Default LLM model used for idle classification when [`ENV_LLM_MODEL`] is unset.
///
/// Why: a cheap, capable default keeps token spend low for the common case while
/// still letting operators opt into a stronger model via [`ENV_LLM_MODEL`].
/// What: the fallback model id passed to the classifier when the env var is absent.
/// Test: `default_llm_model_is_documented`.
pub const DEFAULT_LLM_MODEL: &str = "openai/gpt-4o-mini";
/// Environment variable that toggles idle-session activity classification.
///
/// Why: classification calls the activity monitor (which may call an LLM). On a
/// host with no `OPENROUTER_API_KEY`, or when an operator wants zero token spend,
/// this lets them turn classification off while keeping auto-resume + metrics.
/// What: set to `0` / `false` to disable; defaults to enabled.
/// Test: `classify_idle_env_parsing`.
pub const ENV_CLASSIFY_IDLE: &str = "TRUSTY_MPM_SUPERVISOR_CLASSIFY";
/// Environment variable that overrides the metrics HTTP bind address.
///
/// Why: operators co-locating several daemons need to move the metrics port off
/// the default to avoid collisions.
/// What: parsed as a `SocketAddr`; invalid / absent values fall back to the default.
/// Test: `metrics_addr_env_parsing`.
pub const ENV_METRICS_ADDR: &str = "TRUSTY_MPM_SUPERVISOR_ADDR";
/// Default poll interval when [`ENV_INTERVAL_SECS`] is unset: 30 seconds.
///
/// Why: 30s balances responsiveness (a stopped session resumes within half a
/// minute) against load (the sweep lists sessions and may classify panes).
/// What: the fallback `Duration` used by [`SupervisorConfig::from_env`].
/// Test: `config_defaults`.
pub const DEFAULT_INTERVAL_SECS: u64 = 30;
/// Default metrics bind address when [`ENV_METRICS_ADDR`] is unset.
///
/// Why: loopback-only by default keeps fleet state off the network unless the
/// operator opts in; `7881` sits next to the daemon's `7880`.
/// What: the fallback address string parsed by [`SupervisorConfig::from_env`].
/// Test: `config_defaults`.
pub const DEFAULT_METRICS_ADDR: &str = "127.0.0.1:7881";
/// Immutable configuration for one supervisor run.
///
/// Why: the loop reads its policy from one value per tick; bundling the knobs in
/// a `Clone + Copy`-friendly struct (it is `Clone`; `SocketAddr` is `Copy`) makes
/// the supervisor trivially testable — a test constructs a config directly rather
/// than mutating process-wide env vars.
/// What: carries the poll [`Duration`], the `auto_resume` gate, the
/// `classify_idle` toggle, and the metrics [`SocketAddr`].
/// Test: `config_defaults` and every loop test constructs one of these.
/// Parse a boolean-ish environment variable via a resolver.
///
/// Why: the env contract accepts `1`/`true`/`yes`/`on` (and their negatives) so
/// operators are not surprised by which spelling works; sharing one parser keeps
/// every boolean flag consistent. Taking the value through a resolver (rather than
/// reading `std::env` directly) keeps [`SupervisorConfig::from_env_with`] testable
/// without process-wide mutation.
/// What: returns `Some(true)` for truthy spellings, `Some(false)` for falsy ones,
/// and `None` when the var is absent or unrecognized (so the caller keeps its default).
/// Test: `env_bool_recognizes_truthy_and_falsy`, `auto_resume_env_parsing`.