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
9 changes: 8 additions & 1 deletion src-tauri/src/agent_sessions/cli/parsers/codex_app_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ use crate::agent_sessions::cli::session_runner::launch_profiles::CliPermissionMo

mod catalog;
pub(crate) use catalog::{
archive_thread, native_codex_app_server_command, register_thread, synchronize_thread,
archive_thread, ensure_project, native_codex_app_server_command, register_thread,
synchronize_thread,
};

/// How long to keep draining after `turn/interrupt` before giving up on a
Expand Down Expand Up @@ -192,6 +193,8 @@ pub struct CodexAppServerTurn {
/// developer channel. Never copied into `turn/start.input`.
pub developer_instructions: Option<String>,
pub working_dir: String,
/// Desktop project resolved from the session repository, not its execution worktree.
pub project_id: Option<String>,
/// Stored codex thread id to resume; `None` starts a fresh thread.
pub resume_thread_id: Option<String>,
/// Base model name for `thread/start` (already variant-mapped).
Expand Down Expand Up @@ -258,6 +261,9 @@ pub(crate) fn build_thread_launch_request(turn: &CodexAppServerTurn) -> (&'stati
params["threadId"] = Value::String(resume_id.clone());
("thread/resume", params)
} else {
if let Some(ref project_id) = turn.project_id {
params["projectId"] = Value::String(project_id.clone());
}
("thread/start", params)
}
}
Expand Down Expand Up @@ -1315,6 +1321,7 @@ pub async fn run_app_server_turn(
"title": "ORGII",
"version": env!("CARGO_PKG_VERSION"),
},
"capabilities": {"experimentalApi": true},
}),
)
.await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,82 @@ fn inspect_suffix_application(
}
}

/// Resolve the Desktop project before creating a managed native thread.
/// Called on a blocking worker: project discovery is bounded and per-creation,
/// never an idle watcher. Existing names/metadata remain owned by Desktop.
pub(crate) fn ensure_project(codex_home: &Path, project_root: &Path) -> Result<String, String> {
use sha2::{Digest, Sha256};

let root = project_root
.canonicalize()
.map_err(|error| format!("resolve Codex project root: {error}"))?;
with_rpc(codex_home, &root, |runtime, client| {
let mut cursor = Value::Null;
let deadline = std::time::Instant::now() + REQUEST_TIMEOUT;
for _ in 0..100 {
if std::time::Instant::now() >= deadline {
return Err("Codex project discovery timed out".to_string());
}
let page = request(
runtime,
client,
"project/list",
json!({
"cursor": cursor, "limit": 100
}),
)?;
let projects = page["data"]
.as_array()
.ok_or_else(|| "Codex project/list returned no project array".to_string())?;
for project in projects {
let roots = project["roots"]
.as_array()
.ok_or_else(|| "Codex project has no roots".to_string())?;
if roots
.iter()
.filter_map(|entry| entry["path"].as_str())
.any(|path| paths_have_same_identity(Path::new(path), &root))
{
return project["id"]
.as_str()
.filter(|id| !id.is_empty())
.map(str::to_string)
.ok_or_else(|| "Codex project has no id".to_string());
}
}
let next = page.get("nextCursor").cloned().unwrap_or(Value::Null);
if next.is_null() {
let name = root
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("ORGII workspace");
let key = format!(
"orgii-project-{:x}",
Sha256::digest(root.to_string_lossy().as_bytes())
);
let created = request(
runtime,
client,
"project/create",
json!({
"idempotencyKey": key, "name": name, "roots": [{"path": root}]
}),
)?;
return created["project"]["id"]
.as_str()
.filter(|id| !id.is_empty())
.map(str::to_string)
.ok_or_else(|| "Codex project/create returned no project id".to_string());
}
if next == cursor {
return Err("Codex project/list repeated its cursor".to_string());
}
cursor = next;
}
Err("Codex project discovery exceeded 100 pages".to_string())
})
}

pub(crate) fn register_thread(
codex_home: &Path,
cwd: &Path,
Expand Down
Loading
Loading