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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ CodexMonitor is a Tauri app for orchestrating multiple Codex agents across local
### Composer & Agent Controls

- Compose with image attachments (picker, drag/drop, paste) and configurable follow-up behavior (`Queue` vs `Steer` while a run is active).
- Select a persistent Codex Home in Settings → Codex, including symlinked directories, independently of the CLI executable.
- Use `Shift+Cmd+Enter` (macOS) or `Shift+Ctrl+Enter` (Windows/Linux) to send the opposite follow-up action for a single message.
- Autocomplete for skills (`$`), prompts (`/prompts:`), reviews (`/review`), and file paths (`@`).
- Model picker, collaboration modes (when enabled), reasoning effort, access mode, and context usage ring.
Expand Down Expand Up @@ -291,18 +292,18 @@ src-tauri/

- Workspaces persist to `workspaces.json` under the app data directory.
- App settings persist to `settings.json` under the app data directory (theme, backend mode/provider, remote endpoints/tokens, Codex path, default access mode, UI scale, follow-up message behavior).
- Feature settings are supported in the UI and synced to `$CODEX_HOME/config.toml` (or `~/.codex/config.toml`) on load/save. Stable: Collaboration modes (`features.collaboration_modes`), personality (`personality`), and Background terminal (`features.unified_exec`). Experimental: Apps (`features.apps`). Steering capability still follows Codex `features.steer`, but follow-up default behavior is controlled in Settings → Composer.
- Feature settings read and update the selected Codex Home's `config.toml`. Stable: Collaboration modes (`features.collaboration_modes`), personality (`personality`), and Background terminal (`features.unified_exec`). Experimental: Apps (`features.apps`). Steering capability still follows Codex `features.steer`, but follow-up default behavior is controlled in Settings → Composer.
- On launch and on window focus, the app reconnects and refreshes thread lists for each workspace.
- Threads are restored by filtering `thread/list` results using the workspace `cwd`.
- Selecting a thread always calls `thread/resume` to refresh messages from disk.
- CLI sessions appear if their `cwd` matches the workspace path; they are not live-streamed unless resumed.
- The app uses `codex app-server` over stdio; see `src-tauri/src/lib.rs` and `src-tauri/src/codex/`.
- The remote daemon entrypoint is `src-tauri/src/bin/codex_monitor_daemon.rs`; RPC routing lives in `src-tauri/src/bin/codex_monitor_daemon/rpc.rs` and domain handlers in `src-tauri/src/bin/codex_monitor_daemon/rpc/`.
- Shared domain logic lives in `src-tauri/src/shared/` (notably `src-tauri/src/shared/git_ui_core/` and `src-tauri/src/shared/workspaces_core/`).
- Codex home resolves from workspace settings (if set), then legacy `.codexmonitor/`, then `$CODEX_HOME`/`~/.codex`.
- Codex Home resolves from the explicit application setting, then `$CODEX_HOME`, then `~/.codex`. Unavailable explicit homes report an error without falling back. Changing the selected home disconnects the shared app-server and reloads the UI. See [Codex Home](docs/codex-home.md).
- Worktree agents live under the app data directory (`worktrees/<workspace-id>`); legacy `.codex-worktrees/` paths remain supported, and the app no longer edits repo `.gitignore` files.
- UI state (panel sizes, reduced transparency toggle, recent thread activity) is stored in `localStorage`.
- Custom prompts load from `$CODEX_HOME/prompts` (or `~/.codex/prompts`) with optional frontmatter description/argument hints.
- Custom prompts load from the selected Codex Home's `prompts` directory with optional frontmatter description/argument hints.

## Tauri IPC Surface

Expand Down
45 changes: 45 additions & 0 deletions docs/codex-home.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Codex Home

Settings → Codex → Codex Home selects the directory used for global configuration,
instructions, prompts, agents, local session usage, and the shared Codex app-server.
The setting is independent of the Codex executable and is saved as `codexHome` in
CodexMonitor's application settings.

Enter an absolute directory path, or select a directory with **Browse home**.
**Validate** displays its canonical path without saving. **Save** validates and
persists the selection. **Use default** clears the field; save to restore default
resolution. Changing environments stops existing workspace app-servers and reloads
the UI, which reconnects workspaces and discovers sessions and models in the new
environment. Active turns stop when the environment changes.

Resolution order:

1. Explicit application Codex Home.
2. The application's inherited `CODEX_HOME` environment variable.
3. `~/.codex`.

The default directory can be created on first use. Explicit application and
environment directories must exist and be accessible. An unavailable selected home,
including an unmounted external volume, produces an error and never selects another
home automatically. Settings remain accessible to correct an unavailable path.

Symlinked home directories are supported: their canonical directory is the boundary
used by file operations. For compatibility with Codex, an existing global
`AGENTS.md` leaf symlink may resolve outside that boundary; the editor reads and
writes its target. Global `config.toml` and workspace `AGENTS.md` remain contained
within their canonical roots, and dangling links are errors. Saving application
preferences does not rewrite unrelated Codex configuration; changing homes does not
copy feature settings or credentials between environments.

The selected home is passed only to child Codex processes. CodexMonitor does not
change the system launch environment. The executable must honor `CODEX_HOME` and
provide its own existing credential mechanism; selecting a home does not copy or
provision credentials. When an app-server reports its home during initialization,
CodexMonitor verifies it against the selection and refuses mismatches, including
launchers that override `CODEX_HOME`. Older app-servers can omit this field; their
executable must honor the supplied environment. Launchers must also avoid rewriting
configuration files to targets outside the selected home.

The Home control applies to the local backend and is disabled in remote mode.
Remote file editors display paths from the connected daemon, whose own persisted
settings determine its home.
30 changes: 27 additions & 3 deletions src-tauri/src/backend/app_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tokio::time::timeout;

use crate::backend::events::{AppServerEvent, EventSink};
use crate::codex::args::parse_codex_args;
use crate::codex::home::validate_reported_codex_home;
use crate::shared::process_core::{kill_child_process_tree, tokio_command};
use crate::types::WorkspaceEntry;

Expand Down Expand Up @@ -699,8 +700,12 @@ pub(crate) fn build_codex_command_with_bin(

pub(crate) async fn check_codex_installation(
codex_bin: Option<String>,
codex_home: Option<&Path>,
) -> Result<Option<String>, String> {
let mut command = build_codex_command_with_bin(codex_bin, None, vec!["--version".to_string()])?;
if let Some(home) = codex_home {
command.env("CODEX_HOME", home);
}
command.stdout(std::process::Stdio::piped());
command.stderr(std::process::Stdio::piped());

Expand Down Expand Up @@ -755,7 +760,7 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
event_sink: E,
) -> Result<Arc<WorkspaceSession>, String> {
let codex_bin = default_codex_bin;
let _ = check_codex_installation(codex_bin.clone()).await?;
let _ = check_codex_installation(codex_bin.clone(), codex_home.as_deref()).await?;

let mut command = build_codex_command_with_bin(
codex_bin,
Expand Down Expand Up @@ -1087,8 +1092,27 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
);
}
};
init_response?;
session.send_notification("initialized", None).await?;
let initialization = async {
let response = init_response?;
if response.get("error").is_some() {
return Err("Codex app-server rejected initialization".to_string());
}
let reported_home = response
.get("result")
.and_then(|result| result.get("codexHome"))
.or_else(|| response.get("codexHome"))
.and_then(Value::as_str);
if let (Some(expected), Some(reported)) = (codex_home.as_deref(), reported_home) {
validate_reported_codex_home(expected, reported)?;
}
session.send_notification("initialized", None).await
}
.await;
if let Err(error) = initialization {
let mut child = session.child.lock().await;
kill_child_process_tree(&mut child).await;
return Err(error);
}

let payload = AppServerEvent {
workspace_id: entry.id.clone(),
Expand Down
Loading