Skip to content
Merged
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
75 changes: 56 additions & 19 deletions src-tauri/src/bin/codex_monitor_daemon.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#[path = "../backend/mod.rs"]
mod backend;
#[path = "../codex_home.rs"]
mod codex_home;
#[path = "../codex_config.rs"]
mod codex_config;
#[path = "../rules.rs"]
mod rules;
#[path = "../storage.rs"]
mod storage;
#[path = "../types.rs"]
Expand Down Expand Up @@ -147,7 +151,7 @@ impl DaemonState {
settings.codex_bin.clone()
};

let codex_home = resolve_codex_home(&entry, None);
let codex_home = codex_home::resolve_workspace_codex_home(&entry, None);
let session = spawn_workspace_session(
entry.clone(),
default_bin,
Expand Down Expand Up @@ -250,7 +254,7 @@ impl DaemonState {
settings.codex_bin.clone()
};

let codex_home = resolve_codex_home(&entry, Some(&parent_entry.path));
let codex_home = codex_home::resolve_workspace_codex_home(&entry, Some(&parent_entry.path));
let session = spawn_workspace_session(
entry.clone(),
default_bin,
Expand Down Expand Up @@ -485,7 +489,7 @@ impl DaemonState {
} else {
None
};
let codex_home = resolve_codex_home(&entry, parent_path.as_deref());
let codex_home = codex_home::resolve_workspace_codex_home(&entry, parent_path.as_deref());
let session = spawn_workspace_session(
entry,
default_bin,
Expand Down Expand Up @@ -708,6 +712,46 @@ impl DaemonState {
session.send_response(request_id, result).await?;
Ok(json!({ "ok": true }))
}

async fn remember_approval_rule(
&self,
workspace_id: String,
command: Vec<String>,
) -> Result<Value, String> {
let command = command
.into_iter()
.map(|item| item.trim().to_string())
.filter(|item| !item.is_empty())
.collect::<Vec<_>>();
if command.is_empty() {
return Err("empty command".to_string());
}

let (entry, parent_path) = {
let workspaces = self.workspaces.lock().await;
let entry = workspaces
.get(&workspace_id)
.ok_or("workspace not found")?
.clone();
let parent_path = entry
.parent_id
.as_ref()
.and_then(|parent_id| workspaces.get(parent_id))
.map(|parent| parent.path.clone());
(entry, parent_path)
};

let codex_home = codex_home::resolve_workspace_codex_home(&entry, parent_path.as_deref())
.or_else(codex_home::resolve_default_codex_home)
.ok_or("Unable to resolve CODEX_HOME".to_string())?;
let rules_path = rules::default_rules_path(&codex_home);
rules::append_prefix_rule(&rules_path, &command)?;

Ok(json!({
"ok": true,
"rulesPath": rules_path,
}))
}
}

fn sort_workspaces(workspaces: &mut [WorkspaceInfo]) {
Expand All @@ -721,22 +765,6 @@ fn sort_workspaces(workspaces: &mut [WorkspaceInfo]) {
});
}

fn resolve_codex_home(entry: &WorkspaceEntry, parent_path: Option<&str>) -> Option<PathBuf> {
if entry.kind.is_worktree() {
if let Some(parent_path) = parent_path {
let legacy_home = PathBuf::from(parent_path).join(".codexmonitor");
if legacy_home.is_dir() {
return Some(legacy_home);
}
}
}
let legacy_home = PathBuf::from(&entry.path).join(".codexmonitor");
if legacy_home.is_dir() {
return Some(legacy_home);
}
None
}

fn should_skip_dir(name: &str) -> bool {
matches!(
name,
Expand Down Expand Up @@ -1079,6 +1107,10 @@ fn parse_optional_string_array(value: &Value, key: &str) -> Option<Vec<String>>
}
}

fn parse_string_array(value: &Value, key: &str) -> Result<Vec<String>, String> {
parse_optional_string_array(value, key).ok_or_else(|| format!("missing `{key}`"))
}

fn parse_optional_value(value: &Value, key: &str) -> Option<Value> {
match value {
Value::Object(map) => map.get(key).cloned(),
Expand Down Expand Up @@ -1259,6 +1291,11 @@ async fn handle_rpc_request(
.respond_to_server_request(workspace_id, request_id, result)
.await
}
"remember_approval_rule" => {
let workspace_id = parse_string(&params, "workspaceId")?;
let command = parse_string_array(&params, "command")?;
state.remember_approval_rule(workspace_id, command).await
}
_ => Err(format!("unknown method: {method}")),
}
}
Expand Down
43 changes: 43 additions & 0 deletions src-tauri/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use crate::backend::app_server::{
build_codex_command_with_bin, build_codex_path_env, check_codex_installation,
spawn_workspace_session as spawn_workspace_session_inner,
};
use crate::codex_home::{resolve_default_codex_home, resolve_workspace_codex_home};
use crate::event_sink::TauriEventSink;
use crate::rules;
use crate::state::AppState;
use crate::types::WorkspaceEntry;

Expand Down Expand Up @@ -378,3 +380,44 @@ pub(crate) async fn respond_to_server_request(
.ok_or("workspace not connected")?;
session.send_response(request_id, result).await
}

#[tauri::command]
pub(crate) async fn remember_approval_rule(
workspace_id: String,
command: Vec<String>,
state: State<'_, AppState>,
) -> Result<Value, String> {
let command = command
.into_iter()
.map(|item| item.trim().to_string())
.filter(|item| !item.is_empty())
.collect::<Vec<_>>();
if command.is_empty() {
return Err("empty command".to_string());
}

let (entry, parent_path) = {
let workspaces = state.workspaces.lock().await;
let entry = workspaces
.get(&workspace_id)
.ok_or("workspace not found")?
.clone();
let parent_path = entry
.parent_id
.as_ref()
.and_then(|parent_id| workspaces.get(parent_id))
.map(|parent| parent.path.clone());
(entry, parent_path)
};

let codex_home = resolve_workspace_codex_home(&entry, parent_path.as_deref())
.or_else(resolve_default_codex_home)
.ok_or("Unable to resolve CODEX_HOME".to_string())?;
let rules_path = rules::default_rules_path(&codex_home);
rules::append_prefix_rule(&rules_path, &command)?;

Ok(json!({
"ok": true,
"rulesPath": rules_path,
}))
}
46 changes: 46 additions & 0 deletions src-tauri/src/codex_home.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::env;
use std::path::PathBuf;

use crate::types::WorkspaceEntry;

pub(crate) fn resolve_workspace_codex_home(
entry: &WorkspaceEntry,
parent_path: Option<&str>,
) -> Option<PathBuf> {
if entry.kind.is_worktree() {
if let Some(parent_path) = parent_path {
let legacy_home = PathBuf::from(parent_path).join(".codexmonitor");
if legacy_home.is_dir() {
return Some(legacy_home);
}
}
}
let legacy_home = PathBuf::from(&entry.path).join(".codexmonitor");
if legacy_home.is_dir() {
return Some(legacy_home);
}
None
}

pub(crate) fn resolve_default_codex_home() -> Option<PathBuf> {
if let Ok(value) = env::var("CODEX_HOME") {
if !value.trim().is_empty() {
return Some(PathBuf::from(value.trim()));
}
}
resolve_home_dir().map(|home| home.join(".codex"))
}

fn resolve_home_dir() -> Option<PathBuf> {
if let Ok(value) = env::var("HOME") {
if !value.trim().is_empty() {
return Some(PathBuf::from(value));
}
}
if let Ok(value) = env::var("USERPROFILE") {
if !value.trim().is_empty() {
return Some(PathBuf::from(value));
}
}
None
}
3 changes: 3 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use tauri::{Manager, WebviewUrl, WebviewWindowBuilder};

mod backend;
mod codex;
mod codex_home;
mod codex_config;
#[cfg(not(target_os = "windows"))]
#[path = "dictation.rs"]
Expand All @@ -15,6 +16,7 @@ mod git;
mod git_utils;
mod local_usage;
mod prompts;
mod rules;
mod settings;
mod state;
mod terminal;
Expand Down Expand Up @@ -239,6 +241,7 @@ pub fn run() {
codex::turn_interrupt,
codex::start_review,
codex::respond_to_server_request,
codex::remember_approval_rule,
codex::resume_thread,
codex::list_threads,
codex::archive_thread,
Expand Down
Loading