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
734 changes: 734 additions & 0 deletions src-tauri/src/agent/codex.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src-tauri/src/agent/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod claude;
pub mod codex;
pub mod opencode;

use crate::session::{Session, SessionsResponse, AgentType};
Expand Down Expand Up @@ -34,6 +35,7 @@ pub fn get_all_sessions() -> SessionsResponse {
let detectors: Vec<Box<dyn AgentDetector>> = vec![
Box::new(claude::ClaudeDetector),
Box::new(opencode::OpenCodeDetector),
Box::new(codex::CodexDetector),
];

let mut all_sessions = Vec::new();
Expand Down
82 changes: 82 additions & 0 deletions src-tauri/src/process/codex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use log::debug;
use std::path::PathBuf;
use std::sync::Mutex;
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System, UpdateKind};

/// Represents a running Codex CLI process
#[derive(Debug, Clone)]
pub struct CodexProcess {
pub pid: u32,
pub cwd: Option<PathBuf>,
pub cpu_usage: f32,
}

// Reuse System instance to get accurate CPU readings (requires previous measurement)
static CODEX_SYSTEM: Mutex<Option<System>> = Mutex::new(None);

/// Find all running Codex CLI processes on the system
pub fn find_codex_processes() -> Vec<CodexProcess> {
debug!("=== Starting Codex process discovery ===");

let mut system_guard = CODEX_SYSTEM.lock().unwrap();

// Initialize system if not already done
let system = system_guard.get_or_insert_with(|| {
debug!("Initializing new System instance for Codex");
System::new_with_specifics(
RefreshKind::new().with_processes(
ProcessRefreshKind::new()
.with_cmd(UpdateKind::Always)
.with_cwd(UpdateKind::Always)
.with_cpu(),
),
)
});

// Refresh process list
system.refresh_processes_specifics(
ProcessesToUpdate::All,
ProcessRefreshKind::new()
.with_cmd(UpdateKind::Always)
.with_cwd(UpdateKind::Always)
.with_cpu(),
);

let mut processes = Vec::new();

for (pid, process) in system.processes() {
let cmd = process.cmd();

// Check if first argument is "codex" or ends with "/codex"
let is_codex = if let Some(first_arg) = cmd.first() {
let first_arg_str = first_arg.to_string_lossy().to_lowercase();
first_arg_str == "codex" || first_arg_str.ends_with("/codex")
} else {
false
};

if is_codex {
let cwd = process.cwd().map(|p| p.to_path_buf());
let cpu = process.cpu_usage();

debug!(
"Found Codex process: pid={}, cpu={:.1}%, cwd={:?}",
pid.as_u32(),
cpu,
cwd
);

processes.push(CodexProcess {
pid: pid.as_u32(),
cwd,
cpu_usage: cpu,
});
}
}

debug!(
"Codex process discovery complete: found {} processes",
processes.len()
);
processes
}
2 changes: 2 additions & 0 deletions src-tauri/src/process/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod claude;
mod codex;

pub use claude::{ClaudeProcess, find_claude_processes, is_orphaned_process};
pub use codex::{CodexProcess, find_codex_processes};
2 changes: 1 addition & 1 deletion src-tauri/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ pub mod parser;
mod status;

pub use model::{AgentType, Session, SessionStatus, SessionsResponse};
pub use parser::{parse_session_file, convert_dir_name_to_path, convert_path_to_dir_name, get_sessions, get_sessions_internal, cleanup_stale_status_entries};
pub use parser::{parse_session_file, convert_dir_name_to_path, convert_path_to_dir_name, get_sessions, get_sessions_internal, get_github_url, cleanup_stale_status_entries};
pub use status::{determine_status, status_sort_priority, has_tool_use, has_tool_result, is_local_slash_command, is_interrupted_request};
1 change: 1 addition & 0 deletions src-tauri/src/session/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
pub enum AgentType {
Claude,
OpenCode,
Codex,
}

/// Represents a Claude Code session
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/session/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn get_content_preview(content: &serde_json::Value) -> String {
}

/// Get GitHub URL from a project's git remote origin
fn get_github_url(project_path: &str) -> Option<String> {
pub fn get_github_url(project_path: &str) -> Option<String> {
let output = Command::new("git")
.args(["remote", "get-url", "origin"])
.current_dir(project_path)
Expand Down
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function App() {
</div>
<h2 className="text-lg font-medium text-foreground mb-2">No active sessions</h2>
<p className="text-muted-foreground text-sm max-w-xs">
Start a Claude session in your terminal to see it here
Start an agent session in your terminal to see it here
</p>
</div>
) : (
Expand Down
15 changes: 13 additions & 2 deletions src/components/SessionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,23 @@ const OpenCodeIcon = ({ className }: { className?: string }) => (
</svg>
);

// Agent icon - Claude always orange (brand color), OpenCode uses status color
const AgentStatusIcon = ({ type, statusColor }: { type: 'claude' | 'opencode', statusColor: string }) => {
// OpenAI logo for Codex CLI
const CodexIcon = ({ className }: { className?: string }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="currentColor" className={className || "w-4 h-4"}>
<path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.896zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/>
</svg>
);

// Agent icon - Claude always orange (brand color), Codex green (OpenAI), OpenCode uses status color
const AgentStatusIcon = ({ type, statusColor }: { type: 'claude' | 'opencode' | 'codex', statusColor: string }) => {
if (type === 'claude') {
// Claude brand color: coral/orange #D77655
return <ClaudeIcon className="w-4 h-4 fill-[#D77655]" />;
}
if (type === 'codex') {
return <CodexIcon className="w-4 h-4 text-foreground" />;
}
return <OpenCodeIcon className={`w-4 h-4 ${statusColor}`} />;
};

Expand Down
2 changes: 1 addition & 1 deletion src/types/session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type SessionStatus = 'waiting' | 'processing' | 'thinking' | 'idle';

export type AgentType = 'claude' | 'opencode';
export type AgentType = 'claude' | 'opencode' | 'codex';

export interface Session {
id: string;
Expand Down