Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3b7f4d2
Add report generation for bug sweep
khai-oai Oct 17, 2025
88ccfb4
Restore /secreview command and security review UI
khai-oai Oct 17, 2025
c2a6347
Provide default scope prompts for auto scope confirmation
khai-oai Oct 17, 2025
226ef2e
Restrict security review autoscope to scoped options
khai-oai Oct 17, 2025
b6bdc72
Default scoped review prompts when no paths provided
khai-oai Oct 17, 2025
9f091e7
Allow cancelling in-progress security review with Ctrl-C
khai-oai Oct 17, 2025
23169b8
Improve default autoscope prompts for scoped reviews
khai-oai Oct 17, 2025
e4cbd96
Auto-scope confirmation now displays LLM-chosen directories directly
khai-oai Oct 17, 2025
33e1730
Inline security report viewer JS deps
khai-oai Oct 17, 2025
1cf5334
Restore auto-scope confirmation dialog
khai-oai Oct 17, 2025
ad88013
Provide default auto-scope prompt and confirmation dialog
khai-oai Oct 17, 2025
3a90a11
Detect abbreviations in auto-scope prompts
khai-oai Oct 17, 2025
b567e56
Fix inline report markdown encoding
khai-oai Oct 17, 2025
a915190
Cap auto-scope suggestions to up to 20 directories
khai-oai Oct 17, 2025
0b8a3ec
Reword bug-sweep autoscope prompt to focus on critical code paths
khai-oai Oct 17, 2025
62cedda
Add security review follow-up flow
khai-oai Oct 17, 2025
6040759
Improve security review markdown outputs
khai-oai Oct 17, 2025
c0ccfee
Enhance auto scope keyword expansion
khai-oai Oct 17, 2025
eb5b793
wip
khai-oai Oct 17, 2025
633be44
Add security review follow-up prompts
khai-oai Oct 18, 2025
2b25771
Sync bug markdown with reranked severity
khai-oai Oct 18, 2025
ea22460
Improve security review markdown polishing retries
khai-oai Oct 18, 2025
32eb5ad
tui: dedupe/group bugs before risk rerank; normalize+filter pre and p…
khai-oai Oct 18, 2025
f294813
tui: disable auto-scope for /secreview options 1 & 2; remove default …
khai-oai Oct 20, 2025
bde8a63
tui(security_review):
khai-oai Oct 23, 2025
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
4 changes: 4 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pulldown-cmark = "0.10"
rand = "0.9"
ratatui = "0.29.0"
regex-lite = "0.1.7"
regex = "1.11.1"
reqwest = "0.12"
rmcp = { version = "0.8.0", default-features = false }
schemars = "0.8.22"
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ mod tasks;
mod user_notification;
pub mod util;

/// Shared jittered exponential backoff used across Codex retries.
pub fn default_retry_backoff(attempt: u64) -> std::time::Duration {
util::backoff(attempt)
}

pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
pub use command_safety::is_safe_command;
pub use safety::get_platform_sandbox;
Expand Down
44 changes: 43 additions & 1 deletion codex-rs/core/src/tools/handlers/grep_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ async fn run_rg_search(
limit: usize,
cwd: &Path,
) -> Result<Vec<String>, FunctionCallError> {
// First attempt: regex search
let mut command = Command::new("rg");
command
.current_dir(cwd)
Expand Down Expand Up @@ -146,8 +147,49 @@ async fn run_rg_search(
Some(1) => Ok(Vec::new()),
_ => {
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr_trimmed = stderr.trim();
// Retry with fixed-strings if the regex failed to parse.
if stderr_trimmed.contains("regex parse error")
|| stderr_trimmed.contains("error parsing regex")
|| stderr_trimmed.contains("unclosed group")
{
let mut fixed = Command::new("rg");
fixed
.current_dir(cwd)
.arg("--files-with-matches")
.arg("--sortr=modified")
.arg("--fixed-strings")
.arg(pattern)
.arg("--no-messages");
if let Some(glob) = include {
fixed.arg("--glob").arg(glob);
}
fixed.arg("--").arg(search_path);
let second = timeout(COMMAND_TIMEOUT, fixed.output())
.await
.map_err(|_| {
FunctionCallError::RespondToModel(
"rg timed out after 30 seconds".to_string(),
)
})?
.map_err(|err| {
FunctionCallError::RespondToModel(format!(
"failed to launch rg: {err}. Ensure ripgrep is installed and on PATH."
))
})?;
return match second.status.code() {
Some(0) => Ok(parse_results(&second.stdout, limit)),
Some(1) => Ok(Vec::new()),
_ => {
let second_stderr = String::from_utf8_lossy(&second.stderr);
Err(FunctionCallError::RespondToModel(format!(
"rg failed: {second_stderr}"
)))
}
};
}
Err(FunctionCallError::RespondToModel(format!(
"rg failed: {stderr}"
"rg failed: {stderr_trimmed}"
)))
}
}
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ ratatui = { workspace = true, features = [
"unstable-widget-ref",
] }
regex-lite = { workspace = true }
regex = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
shlex = { workspace = true }
Expand All @@ -71,6 +72,7 @@ textwrap = { workspace = true }
tree-sitter-highlight = { workspace = true }
tree-sitter-bash = { workspace = true }
tokio = { workspace = true, features = [
"fs",
"io-std",
"macros",
"process",
Expand All @@ -85,6 +87,9 @@ opentelemetry-appender-tracing = { workspace = true }
unicode-segmentation = { workspace = true }
unicode-width = { workspace = true }
url = { workspace = true }
futures = { workspace = true }
reqwest = { workspace = true }
time = { workspace = true, features = ["serde"] }

[target.'cfg(unix)'.dependencies]
libc = { workspace = true }
Expand Down
53 changes: 53 additions & 0 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,59 @@ impl App {
AppEvent::OpenReviewCustomPrompt => {
self.chat_widget.show_review_custom_prompt();
}
AppEvent::OpenSecurityReviewPathPrompt(mode) => {
self.chat_widget.show_security_review_path_prompt(mode);
}
AppEvent::StartSecurityReview {
mode,
include_paths,
scope_prompt,
force_new,
} => {
self.chat_widget.start_security_review(
mode,
include_paths,
scope_prompt,
force_new,
);
}
AppEvent::ResumeSecurityReview {
output_root,
metadata,
} => {
self.chat_widget
.resume_security_review(output_root, metadata);
}
AppEvent::SecurityReviewAutoScopeConfirm {
mode,
prompt,
selections,
responder,
} => {
self.chat_widget
.show_security_review_scope_confirmation(mode, prompt, selections, responder);
}
AppEvent::SecurityReviewScopeResolved { paths } => {
self.chat_widget.on_security_review_scope_resolved(paths);
}
AppEvent::SecurityReviewCommandStatus {
id,
summary,
state,
preview,
} => {
self.chat_widget
.on_security_review_command_status(id, summary, state, preview);
}
AppEvent::SecurityReviewLog(message) => {
self.chat_widget.on_security_review_log(message);
}
AppEvent::SecurityReviewComplete { result } => {
self.chat_widget.on_security_review_complete(result);
}
AppEvent::SecurityReviewFailed { error } => {
self.chat_widget.on_security_review_failed(error);
}
AppEvent::FullScreenApprovalRequest(request) => match request {
ApprovalRequest::ApplyPatch { cwd, changes, .. } => {
let _ = tui.enter_alt_screen();
Expand Down
76 changes: 73 additions & 3 deletions codex-rs/tui/src/app_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,31 @@ use codex_core::protocol::ConversationPathResponseEvent;
use codex_core::protocol::Event;
use codex_file_search::FileMatch;

use crate::bottom_pane::ApprovalRequest;
use crate::history_cell::HistoryCell;

use codex_core::protocol::AskForApproval;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort;
use tokio::sync::oneshot;

use crate::bottom_pane::ApprovalRequest;
use crate::history_cell::HistoryCell;
use crate::security_review::SecurityReviewFailure;
use crate::security_review::SecurityReviewMetadata;
use crate::security_review::SecurityReviewMode;
use crate::security_review::SecurityReviewResult;

#[derive(Clone, Debug)]
pub(crate) struct SecurityReviewAutoScopeSelection {
pub display_path: String,
pub reason: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum SecurityReviewCommandState {
Running,
Matches,
NoMatches,
Error,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
Expand Down Expand Up @@ -87,4 +106,55 @@ pub(crate) enum AppEvent {

/// Open the approval popup.
FullScreenApprovalRequest(ApprovalRequest),

/// Open the scoped path input for security reviews.
OpenSecurityReviewPathPrompt(SecurityReviewMode),

/// Begin running a security review with the given mode and optional scoped paths.
StartSecurityReview {
mode: SecurityReviewMode,
include_paths: Vec<String>,
scope_prompt: Option<String>,
force_new: bool,
},

/// Resume a previously generated security review from disk.
ResumeSecurityReview {
output_root: PathBuf,
metadata: SecurityReviewMetadata,
},

/// Prompt the user to confirm auto-detected scope selections.
SecurityReviewAutoScopeConfirm {
mode: SecurityReviewMode,
prompt: String,
selections: Vec<SecurityReviewAutoScopeSelection>,
responder: oneshot::Sender<bool>,
},

/// Notify that the security review scope has been resolved to specific paths.
SecurityReviewScopeResolved {
paths: Vec<String>,
},

/// Update the command status display for running security review shell commands.
SecurityReviewCommandStatus {
id: u64,
summary: String,
state: SecurityReviewCommandState,
preview: Vec<String>,
},

/// Append a progress log emitted during the security review.
SecurityReviewLog(String),

/// Security review completed successfully.
SecurityReviewComplete {
result: SecurityReviewResult,
},

/// Security review failed prior to completion.
SecurityReviewFailed {
error: SecurityReviewFailure,
},
}
4 changes: 4 additions & 0 deletions codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,10 @@ impl ChatComposer {
self.is_task_running = running;
}

pub(crate) fn set_placeholder_text(&mut self, placeholder: String) {
self.placeholder_text = placeholder;
}

pub(crate) fn set_context_window_percent(&mut self, percent: Option<u8>) {
if self.context_window_percent != percent {
self.context_window_percent = percent;
Expand Down
31 changes: 31 additions & 0 deletions codex-rs/tui/src/bottom_pane/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ pub(crate) use list_selection_view::SelectionViewParams;
mod paste_burst;
pub mod popup_consts;
mod scroll_state;
mod security_review_scope_confirm_view;
mod selection_popup_common;
mod textarea;

pub(crate) use security_review_scope_confirm_view::SecurityReviewScopeConfirmView;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CancellationEvent {
Handled,
Expand All @@ -44,6 +47,7 @@ pub(crate) use chat_composer::InputResult;
use codex_protocol::custom_prompts::CustomPrompt;

use crate::status_indicator_widget::StatusIndicatorWidget;
pub(crate) use crate::status_indicator_widget::StatusSnapshot;
pub(crate) use list_selection_view::SelectionAction;
pub(crate) use list_selection_view::SelectionItem;

Expand All @@ -68,6 +72,8 @@ pub(crate) struct BottomPane {
status: Option<StatusIndicatorWidget>,
/// Queued user messages to show under the status indicator.
queued_user_messages: Vec<String>,
/// Recent log messages shown beneath the status header.
status_logs: Vec<String>,
context_window_percent: Option<u8>,
}

Expand Down Expand Up @@ -100,6 +106,7 @@ impl BottomPane {
ctrl_c_quit_hint: false,
status: None,
queued_user_messages: Vec::new(),
status_logs: Vec::new(),
esc_backtrack_hint: false,
context_window_percent: None,
}
Expand Down Expand Up @@ -270,6 +277,11 @@ impl BottomPane {
self.request_redraw();
}

pub(crate) fn set_placeholder_text(&mut self, text: String) {
self.composer.set_placeholder_text(text);
self.request_redraw();
}

/// Get the current composer text (for tests and programmatic checks).
pub(crate) fn composer_text(&self) -> String {
self.composer.current_text()
Expand All @@ -285,6 +297,22 @@ impl BottomPane {
}
}

pub(crate) fn update_status_snapshot(&mut self, snapshot: StatusSnapshot) {
self.status_logs = snapshot.logs.clone();
if let Some(status) = self.status.as_mut() {
status.update_snapshot(snapshot);
} else {
self.update_status_header(snapshot.header);
}
}

pub(crate) fn update_status_logs(&mut self, logs: Vec<String>) {
self.status_logs = logs.clone();
if let Some(status) = self.status.as_mut() {
status.set_logs(logs);
}
}

pub(crate) fn show_ctrl_c_quit_hint(&mut self) {
self.ctrl_c_quit_hint = true;
self.composer
Expand Down Expand Up @@ -328,18 +356,21 @@ impl BottomPane {

if running {
if self.status.is_none() {
self.status_logs.clear();
self.status = Some(StatusIndicatorWidget::new(
self.app_event_tx.clone(),
self.frame_requester.clone(),
));
}
if let Some(status) = self.status.as_mut() {
status.set_queued_messages(self.queued_user_messages.clone());
status.set_logs(self.status_logs.clone());
}
self.request_redraw();
} else {
// Hide the status indicator when a task completes, but keep other modal views.
self.hide_status_indicator();
self.status_logs.clear();
}
}

Expand Down
Loading