codex cloud

This commit is contained in:
easong-openai
2025-09-05 15:16:52 -07:00
parent d1cf46b09f
commit 35dec89d8a
6 changed files with 54 additions and 60 deletions

View File

@@ -79,7 +79,8 @@ enum Subcommand {
GenerateTs(GenerateTsCommand),
/// Browse and apply tasks from the cloud.
CloudTasks(CloudTasksCli),
#[clap(name = "cloud", alias = "cloud-tasks")]
Cloud(CloudTasksCli),
}
#[derive(Debug, Parser)]
@@ -191,7 +192,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
Some(Subcommand::Completion(completion_cli)) => {
print_completion(completion_cli);
}
Some(Subcommand::CloudTasks(mut cloud_cli)) => {
Some(Subcommand::Cloud(mut cloud_cli)) => {
prepend_config_flags(&mut cloud_cli.config_overrides, cli.config_overrides);
codex_cloud_tasks::run_main(cloud_cli, codex_linux_sandbox_exe).await?;
}

View File

@@ -36,7 +36,7 @@ async fn main() -> anyhow::Result<()> {
|| base_url.starts_with("https://chat.openai.com"))
&& !base_url.contains("/backend-api")
{
base_url = format!("{base_url}/backend-api");
base_url = format!("{base_url}/backend-api");
}
println!("base_url: {base_url}");
println!(

View File

@@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> {
|| base_url.starts_with("https://chat.openai.com"))
&& !base_url.contains("/backend-api")
{
base_url = format!("{base_url}/backend-api");
base_url = format!("{base_url}/backend-api");
}
println!("base_url: {base_url}");
let is_wham = base_url.contains("/backend-api");

View File

@@ -95,9 +95,7 @@ pub async fn autodetect_environment_id(
anyhow::bail!("GET {list_url} failed: {status}; content-type={ct}; body={body}");
}
let all_envs: Vec<CodeEnvironment> = serde_json::from_str(&body).map_err(|e| {
anyhow::anyhow!(
"Decode error for {list_url}: {e}; content-type={ct}; body={body}"
)
anyhow::anyhow!("Decode error for {list_url}: {e}; content-type={ct}; body={body}")
})?;
if let Some(env) = pick_environment_row(&all_envs, desired_label.as_deref()) {
return Ok(AutodetectSelection {
@@ -230,9 +228,7 @@ fn parse_owner_repo(url: &str) -> Option<(String, String)> {
let mut parts = rest.splitn(2, '/');
let owner = parts.next()?.to_string();
let repo = parts.next()?.to_string();
crate::append_error_log(format!(
"env: parsed SSH GitHub origin => {owner}/{repo}"
));
crate::append_error_log(format!("env: parsed SSH GitHub origin => {owner}/{repo}"));
return Some((owner, repo));
}
// HTTPS or git protocol
@@ -247,9 +243,7 @@ fn parse_owner_repo(url: &str) -> Option<(String, String)> {
let mut parts = rest.splitn(2, '/');
let owner = parts.next()?.to_string();
let repo = parts.next()?.to_string();
crate::append_error_log(format!(
"env: parsed HTTP GitHub origin => {owner}/{repo}"
));
crate::append_error_log(format!("env: parsed HTTP GitHub origin => {owner}/{repo}"));
return Some((owner, repo));
}
}

View File

@@ -31,7 +31,7 @@ pub(crate) fn append_error_log(message: impl AsRef<str>) {
// (no standalone patch summarizer needed UI displays raw diffs)
/// Entry point for the `codex cloud-tasks` subcommand.
/// Entry point for the `codex cloud` subcommand.
pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()> {
// Very minimal logging setup; mirrors other crates' pattern.
let default_level = "error";
@@ -107,7 +107,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
}
_ => {
eprintln!(
"Not signed in. Please run 'codex login' to sign in with ChatGPT, then re-run 'codex cloud-tasks'."
"Not signed in. Please run 'codex login' to sign in with ChatGPT, then re-run 'codex cloud'."
);
std::process::exit(1);
}
@@ -115,7 +115,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
}
None => {
eprintln!(
"Not signed in. Please run 'codex login' to sign in with ChatGPT, then re-run 'codex cloud-tasks'."
"Not signed in. Please run 'codex login' to sign in with ChatGPT, then re-run 'codex cloud'."
);
std::process::exit(1);
}
@@ -351,8 +351,8 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
// Render helper to centralize immediate redraws after handling events.
let render_if_needed = |terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
app: &mut app::App,
needs_redraw: &mut bool|
app: &mut app::App,
needs_redraw: &mut bool|
-> anyhow::Result<()> {
if *needs_redraw {
terminal.draw(|f| ui::draw(f, app))?;
@@ -1204,48 +1204,48 @@ fn pretty_lines_from_error(raw: &str) -> Vec<String> {
if let Some(body_idx) = raw.find(" body=")
&& let Some(json_start_rel) = raw[body_idx..].find('{')
{
let json_start = body_idx + json_start_rel;
let json_str = raw[json_start..].trim();
if let Ok(v) = serde_json::from_str::<serde_json::Value>(json_str) {
// Prefer assistant turn context.
let turn = v
.get("current_assistant_turn")
.and_then(|x| x.as_object())
.cloned()
.or_else(|| {
v.get("current_diff_task_turn")
.and_then(|x| x.as_object())
.cloned()
});
if let Some(t) = turn {
if let Some(err) = t.get("error").and_then(|e| e.as_object()) {
let code = err.get("code").and_then(|s| s.as_str()).unwrap_or("");
let msg = err.get("message").and_then(|s| s.as_str()).unwrap_or("");
if !code.is_empty() || !msg.is_empty() {
let summary = if code.is_empty() {
msg.to_string()
} else if msg.is_empty() {
code.to_string()
} else {
format!("{code}: {msg}")
};
lines.push(format!("Assistant error: {summary}"));
}
}
if let Some(status) = t.get("turn_status").and_then(|s| s.as_str()) {
lines.push(format!("Status: {status}"));
}
if let Some(text) = t
.get("latest_event")
.and_then(|e| e.get("text"))
.and_then(|s| s.as_str())
&& !text.trim().is_empty()
{
lines.push(format!("Latest event: {}", text.trim()));
let json_start = body_idx + json_start_rel;
let json_str = raw[json_start..].trim();
if let Ok(v) = serde_json::from_str::<serde_json::Value>(json_str) {
// Prefer assistant turn context.
let turn = v
.get("current_assistant_turn")
.and_then(|x| x.as_object())
.cloned()
.or_else(|| {
v.get("current_diff_task_turn")
.and_then(|x| x.as_object())
.cloned()
});
if let Some(t) = turn {
if let Some(err) = t.get("error").and_then(|e| e.as_object()) {
let code = err.get("code").and_then(|s| s.as_str()).unwrap_or("");
let msg = err.get("message").and_then(|s| s.as_str()).unwrap_or("");
if !code.is_empty() || !msg.is_empty() {
let summary = if code.is_empty() {
msg.to_string()
} else if msg.is_empty() {
code.to_string()
} else {
format!("{code}: {msg}")
};
lines.push(format!("Assistant error: {summary}"));
}
}
if let Some(status) = t.get("turn_status").and_then(|s| s.as_str()) {
lines.push(format!("Status: {status}"));
}
if let Some(text) = t
.get("latest_event")
.and_then(|e| e.get("text"))
.and_then(|s| s.as_str())
&& !text.trim().is_empty()
{
lines.push(format!("Latest event: {}", text.trim()));
}
}
}
}
if lines.len() == 1 {
// Parsing yielded nothing; include a trimmed, short raw message tail for context.

View File

@@ -95,7 +95,6 @@ fn overlay_content(area: Rect) -> Rect {
}
pub fn draw_new_task_page(frame: &mut Frame, area: Rect, app: &mut App) {
let title_spans = {
let mut spans: Vec<ratatui::text::Span> = vec!["New Task".magenta().bold()];
if let Some(id) = app
@@ -172,7 +171,7 @@ fn draw_list(frame: &mut Frame, area: Rect, app: &mut App) {
.find(|r| &r.id == id)
.and_then(|r| r.label.clone())
.unwrap_or_else(|| "Selected".to_string());
format!("{label}").dim()
format!("{label}").dim()
} else {
" • All".dim()
};
@@ -181,7 +180,7 @@ fn draw_list(frame: &mut Frame, area: Rect, app: &mut App) {
" • 0%".dim()
} else {
let p = ((app.selected as f32) / ((app.tasks.len() - 1) as f32) * 100.0).round() as i32;
format!("{}%", p.clamp(0, 100)).dim()
format!("{}%", p.clamp(0, 100)).dim()
};
let title_line = {
let base = Line::from(vec!["Cloud Tasks".into(), suffix_span, percent_span]);
@@ -452,7 +451,7 @@ pub fn draw_apply_modal(frame: &mut Frame, area: Rect, app: &mut App) {
body_lines.push(Line::from(""));
body_lines.push(
Line::from(format!("Skipped ({}):", m.skipped_paths.len()))
.magenta()
.magenta()
.bold(),
);
for p in &m.skipped_paths {