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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Data shapes shared by the collector, the TUI, and the JSON output path.
use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Busy,
Spawning,
Active,
Idle,
Waiting,
Completed,
Stale,
}
impl Status {
pub fn rank(self) -> u8 {
match self {
Status::Busy => 0,
Status::Spawning => 1,
Status::Active => 2,
Status::Idle => 3,
Status::Waiting => 4,
Status::Completed => 5,
Status::Stale => 6,
}
}
pub fn label(self) -> &'static str {
match self {
Status::Busy => "BUSY",
Status::Spawning => "SPWN",
Status::Active => "ACTV",
Status::Idle => "idle",
Status::Waiting => "WAIT",
Status::Completed => "DONE",
Status::Stale => "stale",
}
}
pub fn glyph(self) -> &'static str {
match self {
Status::Busy | Status::Active => "●",
Status::Spawning => "●",
Status::Idle => "○",
Status::Waiting => "◌",
Status::Completed => "✓",
Status::Stale => "·",
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Agent {
pub pid: u32,
pub label: String,
pub status: Status,
pub project: String,
pub current_tool: Option<String>,
pub current_task: Option<String>,
pub subagents: u32,
/// Human-readable descriptions of in-flight Task / Agent tool calls
/// (e.g. "code-reviewer: review the auth refactor"). Populated by the
/// session reader; one entry per element in `subagents`.
pub in_flight_subagents: Vec<String>,
/// Last ~6–10 readable events from the session transcript, oldest →
/// newest. Each line is already prefixed with one of:
/// `› ` user / assistant prose
/// `→ ` tool call
/// `← ` tool result
/// Surfaced in the detail popup as a live-preview box.
pub recent_activity: Vec<String>,
pub session_id: Option<String>,
pub session_age_ms: Option<u64>,
/// Sum of input + output (+ cache) tokens charged to this agent's session.
pub tokens_total: u64,
/// Total input bucket: standard_input + cache_read + cache_creation.
/// (Use `tokens_input - tokens_cache_read - tokens_cache_write` if
/// you specifically want the *uncached* portion.)
pub tokens_input: u64,
pub tokens_output: u64,
/// Cache-read tokens, charged at ~10% of the standard input rate
/// under Anthropic prompt-caching pricing. Tracked separately so
/// `cost_usd` applies the discount instead of over-billing cache
/// hits at the full input rate.
#[serde(default)]
pub tokens_cache_read: u64,
/// Cache-creation / cache-write tokens, charged at ~125% of the
/// standard input rate.
#[serde(default)]
pub tokens_cache_write: u64,
pub cost_usd: f64,
/// How `cost_usd` was computed. One of:
/// - "api" — known per-token rate looked up in the price table
/// - "local" — model runs on user's hardware (Ollama / vLLM / llama.cpp); $0
/// - "unknown" — no model name, or no price-table match (treated as $0 but flagged)
pub cost_basis: String,
pub model: Option<String>,
/// Latest-turn input window size in tokens (input_tokens +
/// cache_read_input_tokens for Anthropic, prompt_tokens for
/// OpenAI / Codex). Drives the per-agent context-fill bar in the
/// detail popup so the user can see how close they are to
/// auto-compaction. 0 if unknown.
pub context_used: u64,
/// Maximum input window in tokens for this agent's model, looked
/// up via the bundled price table (LiteLLM-derived). 0 if unknown.
pub context_limit: u64,
/// Names of Claude Code agent skills loaded for this session.
/// Populated by scanning `~/.claude/skills/<name>/SKILL.md` plus
/// `<cwd>/.claude/skills/<name>/SKILL.md`. Empty for non-Claude
/// vendors.
pub loaded_skills: Vec<String>,
/// Process is running with elevated / unsafe permissions
/// (e.g. `claude --dangerously-skip-permissions`, `--yolo`, `--no-permissions`).
/// The TUI surfaces this as a pulsating "GOD" tag on the row.
pub dangerous: bool,
/// Recent CPU% samples for the inline sparkline (oldest → newest).
pub cpu_history: Vec<f64>,
pub cpu: f64,
pub cpu_raw: f64,
pub rss: u64,
pub vsize: u64,
pub threads: u64,
pub state: String,
pub ppid: u32,
pub uptime_sec: u64,
pub cwd: String,
pub exe: String,
pub cmdline: String,
pub read_bytes: u64,
pub write_bytes: u64,
pub writing_files: Vec<String>,
pub writing_dirs: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct ProjectAgg {
pub project: String,
pub agents: u32,
pub cpu: f64,
pub rss: u64,
pub subagents: u32,
pub tokens_total: u64,
pub cost_usd: f64,
pub statuses: HashMap<&'static str, u32>,
pub cwd: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct Session {
pub id: String,
pub project: String,
pub project_short: String,
pub file: String,
pub size_bytes: u64,
pub mtime_ms: u64,
pub age_ms: u64,
pub status: Status,
pub stop_reason: Option<String>,
pub last_task: Option<String>,
pub last_tool: Option<String>,
pub current_tool: Option<String>,
pub in_flight_tasks: u32,
pub in_flight_subagents: Vec<String>,
pub recent_activity: Vec<String>,
pub live_pid: Option<u32>,
pub is_most_recent: bool,
pub tokens_total: u64,
pub tokens_input: u64,
pub tokens_output: u64,
/// See `Agent::tokens_cache_read`.
#[serde(default)]
pub tokens_cache_read: u64,
/// See `Agent::tokens_cache_write`.
#[serde(default)]
pub tokens_cache_write: u64,
pub cost_usd: f64,
pub model: Option<String>,
/// Latest-turn input window size (see `Agent::context_used`).
pub context_used: u64,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Sessions {
pub sessions: Vec<Session>,
pub recent_tasks: Vec<RecentTask>,
pub active: u32,
pub busy: u32,
pub waiting: u32,
pub completed: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct RecentTask {
pub project: String,
pub project_short: String,
pub task: String,
pub mtime_ms: u64,
pub status: Status,
}
#[derive(Debug, Clone, Serialize)]
pub struct ActivityEvent {
pub t: u64,
pub kind: ActivityKind,
pub label: String,
pub pid: u32,
pub cwd: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ActivityKind {
Spawn,
Exit,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Aggregates {
pub cpu: f64,
pub mem_bytes: u64,
pub active: u32,
pub busy: u32,
pub waiting: u32,
pub completed: u32,
pub subagents: u32,
pub project_count: u32,
pub tokens_total: u64,
pub tokens_input: u64,
pub tokens_output: u64,
pub cost_usd: f64,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct History {
pub total: Vec<f64>,
pub active: Vec<f64>,
pub busy: Vec<f64>,
pub cpu: Vec<f64>,
pub mem: Vec<f64>,
/// Per-tick *delta* in cumulative tokens — gives a true activity pulse.
pub tokens_rate: Vec<f64>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Snapshot {
pub now: u64,
pub platform: String,
pub note: Option<String>,
pub sys_cpus: u32,
pub mem_total: u64,
pub mem_available: u64,
pub aggregates: Aggregates,
pub agents: Vec<Agent>,
pub projects: Vec<ProjectAgg>,
pub sessions: Sessions,
pub history: History,
pub activity: Vec<ActivityEvent>,
}