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
//! [`AgentUsageConfig`]: agent usage panel settings.
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
/// Configuration for the agent-usage status-bar widget and popup panel.
///
/// The panel is a display over a records directory any collector can write
/// (see `docs/features/AGENT_USAGE.md`); these keys arm the subsystem and its
/// optional refresh command, they never identify collectors themselves.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct AgentUsageConfig {
/// Enable the agent-usage subsystem (directory watch + panel). The
/// status-bar widget still self-hides when no records are displayable;
/// this switch is the master arm for the whole feature.
#[serde(default = "default_agent_usage_enabled")]
pub agent_usage_enabled: bool,
/// Optional command run through `sh -c` on the refresh interval and on
/// manual refresh, expected to rewrite the records directory (omarchy's
/// `omarchy-agent-usage-update` division of labor). Empty/absent means
/// pure watch mode — par-term runs nothing and only reacts to file
/// changes. par-term ships no collectors of its own.
#[serde(default)]
pub agent_usage_update_command: Option<String>,
/// Seconds between refresh-driven rescans (and update-command runs, when
/// configured). Clamped to a 30 s floor at the call site.
#[serde(default = "default_agent_usage_refresh_interval_sec")]
pub agent_usage_refresh_interval_sec: u64,
/// Agent ids to hide from the widget and panel (the hide-list form of
/// the design's default-true per-agent enable map).
#[serde(default)]
pub agent_usage_hidden_agents: Vec<String>,
}
fn default_agent_usage_enabled() -> bool {
true
}
fn default_agent_usage_refresh_interval_sec() -> u64 {
300
}
impl Default for AgentUsageConfig {
fn default() -> Self {
Self {
agent_usage_enabled: default_agent_usage_enabled(),
agent_usage_update_command: None,
agent_usage_refresh_interval_sec: default_agent_usage_refresh_interval_sec(),
agent_usage_hidden_agents: Vec::new(),
}
}
}
impl AgentUsageConfig {
/// The hidden-agent set, as the usage store consumes it.
pub fn hidden_set(&self) -> HashSet<String> {
self.agent_usage_hidden_agents.iter().cloned().collect()
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_pure_watch_mode() {
let config = AgentUsageConfig::default();
assert!(config.agent_usage_enabled);
assert_eq!(config.agent_usage_update_command, None);
assert_eq!(config.agent_usage_refresh_interval_sec, 300);
assert!(config.agent_usage_hidden_agents.is_empty());
}
#[test]
fn yaml_keys_parse_at_top_level() {
// The sub-config is #[serde(flatten)]-ed into Config, so its keys sit
// at the YAML top level; verify they parse with the exact spellings
// the docs will publish.
let yaml = r#"
agent_usage_enabled: false
agent_usage_update_command: "my-collector --update"
agent_usage_refresh_interval_sec: 60
agent_usage_hidden_agents:
- claude
- codex
"#;
let parsed: AgentUsageConfig = serde_yaml_ng::from_str(yaml).expect("keys parse");
assert!(!parsed.agent_usage_enabled);
assert_eq!(
parsed.agent_usage_update_command.as_deref(),
Some("my-collector --update")
);
assert_eq!(parsed.agent_usage_refresh_interval_sec, 60);
assert_eq!(parsed.agent_usage_hidden_agents, vec!["claude", "codex"]);
assert!(parsed.hidden_set().contains("claude"));
}
}