Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ All read-only. No API keys. No auth.

### macOS / Linux

> [!IMPORTANT]
> On Linux, ensure `sqlite3` is installed to enable monitoring for OpenCode sessions.
```bash
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/graykode/abtop/releases/latest/download/abtop-installer.sh | sh
```
Expand Down
24 changes: 8 additions & 16 deletions src/collector/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ impl ClaudeCollector {
};

let configured_model = read_configured_model(&sf.cwd);
let context_window = context_window_for_model(&model, &configured_model, max_context_tokens);
let context_window = crate::collector::context_window_for_model(&model, &configured_model, max_context_tokens);
let context_percent = if context_window > 0 {
(last_context_tokens as f64 / context_window as f64) * 100.0
} else {
Expand Down Expand Up @@ -1885,14 +1885,6 @@ fn truncate(s: &str, max: usize) -> String {
}
}

fn context_window_for_model(transcript_model: &str, configured_model: &str, max_context_tokens: u64) -> u64 {
if transcript_model.contains("[1m]") || configured_model.contains("[1m]") || max_context_tokens > 200_000 {
1_000_000
} else {
200_000
}
}

/// Returns the ordered list of Claude Code settings files to check, from
/// highest to lowest priority, matching Claude Code's own resolution order:
/// 1. `{cwd}/.claude/settings.local.json`
Expand Down Expand Up @@ -3087,27 +3079,27 @@ n/Users/bob/.claude-alt/projects/-Users-bob-project/session.jsonl
}

#[test]
fn test_context_window_for_model() {
fn test_crate::collector::context_window_for_model() {
// Base model with low token usage → 200K
assert_eq!(context_window_for_model("claude-opus-4-6", "", 50_000), 200_000);
assert_eq!(crate::collector::context_window_for_model("claude-opus-4-6", "", 50_000), 200_000);
// Explicit [1m] suffix in transcript model → 1M regardless of token count
assert_eq!(
context_window_for_model("claude-opus-4-6[1m]", "", 0),
crate::collector::context_window_for_model("claude-opus-4-6[1m]", "", 0),
1_000_000
);
// [1m] in configured model (from settings.json) → 1M even if transcript lacks it
assert_eq!(
context_window_for_model("claude-sonnet-4-6", "sonnet[1m]", 0),
crate::collector::context_window_for_model("claude-sonnet-4-6", "sonnet[1m]", 0),
1_000_000
);
assert_eq!(
context_window_for_model("claude-sonnet-4-6", "", 100_000),
crate::collector::context_window_for_model("claude-sonnet-4-6", "", 100_000),
200_000
);
assert_eq!(context_window_for_model("unknown-model", "", 0), 200_000);
assert_eq!(crate::collector::context_window_for_model("unknown-model", "", 0), 200_000);
// Token usage exceeds 200K → must be 1M window
assert_eq!(
context_window_for_model("claude-opus-4-6", "", 250_000),
crate::collector::context_window_for_model("claude-opus-4-6", "", 250_000),
1_000_000
);
}
Expand Down
31 changes: 7 additions & 24 deletions src/collector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,30 +112,13 @@ pub trait AgentCollector {

/// Process data fetched once per tick and shared across all collectors.
/// Avoids duplicate ps/lsof calls.
pub struct SharedProcessData {
pub process_info: HashMap<u32, process::ProcInfo>,
pub children_map: HashMap<u32, Vec<u32>>,
pub ports: HashMap<u32, Vec<u16>>,
/// True on slow poll ticks (every 5 ticks ≈ 10s). Collectors should
/// defer expensive discovery (e.g. /proc reads) to slow ticks.
pub slow_tick: bool,
/// PIDs of detected codex mcp-server processes. Populated by
/// `MultiCollector` after McpDetection runs; CodexCollector
/// excludes these so a single mcp-server PID isn't double-counted
/// in the sessions panel.
pub mcp_server_pids: HashSet<u32>,
/// Rollout file paths held open by an mcp-server process. The
/// CodexCollector "recently finished" pass skips these to avoid
/// PID=0 ghost rows for threads that the mcp-server is still
/// holding fds for.
pub mcp_owned_rollouts: HashSet<PathBuf>,
/// When false, the suppression sets above are empty so the
/// sessions panel restores upstream behavior. Driven by the user
/// toggle (Shift+M).
pub mcp_suppress: bool,
/// Cached Desktop app-server PID -> open rollout files. This is populated
/// by a background scanner so slow macOS lsof calls cannot block the TUI.
pub desktop_rollout_fd_map: HashMap<u32, Vec<PathBuf>>,
/// Context window size for this model (e.g. 200K, 1M).
pub(crate) fn context_window_for_model(transcript_model: &str, configured_model: &str, max_context_tokens: u64) -> u64 {
if transcript_model.contains("[1m]") || configured_model.contains("[1m]") || max_context_tokens > 200_000 {
1_000_000
} else {
200_000
}
}

impl SharedProcessData {
Expand Down
13 changes: 10 additions & 3 deletions src/collector/opencode.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::process;
use super::{process, context_window_for_model};
use crate::model::{AgentSession, ChildProcess, SessionStatus};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -158,6 +158,13 @@ impl OpenCodeCollector {
"-".to_string()
};

let context_window = context_window_for_model(&model, "", 0);
let context_percent = if context_window > 0 {
((ds.total_input + ds.total_output) as f64 / context_window as f64) * 100.0
} else {
0.0
};

sessions.push(AgentSession {
agent_cli: "opencode",
pid: matched_pid,
Expand All @@ -168,7 +175,7 @@ impl OpenCodeCollector {
status,
model,
effort: String::new(),
context_percent: 0.0,
context_percent,
total_input_tokens: ds.total_input,
total_output_tokens: ds.total_output,
total_cache_read: ds.total_cache_read,
Expand All @@ -183,7 +190,7 @@ impl OpenCodeCollector {
token_history: vec![],
context_history: vec![],
compaction_count: 0,
context_window: 0,
context_window,
subagents: vec![],
mem_file_count: 0,
mem_line_count: 0,
Expand Down