mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
codex cloud
This commit is contained in:
@@ -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?;
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user