mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Add presentation hint for quiet managed hooks
This commit is contained in:
@@ -413,6 +413,7 @@ mod tests {
|
||||
display_order: 0,
|
||||
status: AppServerHookRunStatus::Running,
|
||||
status_message: Some("checking go-workflow input policy".to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
@@ -436,6 +437,7 @@ mod tests {
|
||||
display_order: 0,
|
||||
status: AppServerHookRunStatus::Stopped,
|
||||
status_message: Some("checking go-workflow input policy".to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: Some(11),
|
||||
duration_ms: Some(10),
|
||||
|
||||
@@ -2917,6 +2917,9 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn on_hook_started(&mut self, run: codex_app_server_protocol::HookRunSummary) {
|
||||
if history_cell::hook_run_should_skip_render(&run) {
|
||||
return;
|
||||
}
|
||||
self.flush_answer_stream_with_separator();
|
||||
self.flush_completed_hook_output();
|
||||
match self.active_hook_cell.as_mut() {
|
||||
@@ -2936,6 +2939,9 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn on_hook_completed(&mut self, completed: codex_app_server_protocol::HookRunSummary) {
|
||||
if history_cell::hook_run_should_skip_render(&completed) {
|
||||
return;
|
||||
}
|
||||
let completed_existing_run = self
|
||||
.active_hook_cell
|
||||
.as_mut()
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/status_and_layout.rs
|
||||
expression: "format!(\"{routine_running_snapshot}\\n\\n{routine_completed_snapshot}\\n\\n{failed_snapshot}\")"
|
||||
---
|
||||
routine running
|
||||
live hooks:
|
||||
<empty>
|
||||
history:
|
||||
<empty>
|
||||
|
||||
routine completed
|
||||
live hooks:
|
||||
<empty>
|
||||
history:
|
||||
<empty>
|
||||
|
||||
failed
|
||||
live hooks:
|
||||
<empty>
|
||||
history:
|
||||
• PostToolUse hook (failed)
|
||||
error: hook exited with code 7
|
||||
@@ -1483,6 +1483,7 @@ pub(super) fn hook_run(
|
||||
display_order: 0,
|
||||
status,
|
||||
status_message: Some(status_message.to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: matches!(
|
||||
status,
|
||||
|
||||
@@ -2545,6 +2545,7 @@ async fn user_prompt_submit_app_server_hook_notifications_render_snapshot() {
|
||||
display_order: 0,
|
||||
status: AppServerHookRunStatus::Running,
|
||||
status_message: Some("checking go-workflow input policy".to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
@@ -2568,6 +2569,7 @@ async fn user_prompt_submit_app_server_hook_notifications_render_snapshot() {
|
||||
display_order: 0,
|
||||
status: AppServerHookRunStatus::Stopped,
|
||||
status_message: Some("checking go-workflow input policy".to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: Some(11),
|
||||
duration_ms: Some(10),
|
||||
@@ -2776,6 +2778,58 @@ async fn completed_hook_with_output_flushes_immediately() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn visibility_hidden_hooks_skip_routine_rows_but_render_failures_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
let mut routine_started = hook_started_run(
|
||||
"post-tool-use:0:/tmp/hooks.json",
|
||||
codex_app_server_protocol::HookEventName::PostToolUse,
|
||||
Some("checking output policy"),
|
||||
);
|
||||
routine_started.visibility_hint = codex_app_server_protocol::HookVisibilityHint::Hidden;
|
||||
handle_hook_started(&mut chat, routine_started);
|
||||
reveal_running_hooks(&mut chat);
|
||||
let routine_running_snapshot = hook_live_and_history_snapshot(&chat, "routine running", "");
|
||||
|
||||
let mut routine_completed = hook_completed_run(
|
||||
"post-tool-use:0:/tmp/hooks.json",
|
||||
codex_app_server_protocol::HookEventName::PostToolUse,
|
||||
codex_app_server_protocol::HookRunStatus::Completed,
|
||||
Vec::new(),
|
||||
);
|
||||
routine_completed.visibility_hint = codex_app_server_protocol::HookVisibilityHint::Hidden;
|
||||
handle_hook_completed(&mut chat, routine_completed);
|
||||
let routine_history = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
let routine_completed_snapshot =
|
||||
hook_live_and_history_snapshot(&chat, "routine completed", &routine_history);
|
||||
|
||||
let mut failed_completed = hook_completed_run(
|
||||
"post-tool-use:1:/tmp/hooks.json",
|
||||
codex_app_server_protocol::HookEventName::PostToolUse,
|
||||
codex_app_server_protocol::HookRunStatus::Failed,
|
||||
vec![codex_app_server_protocol::HookOutputEntry {
|
||||
kind: codex_app_server_protocol::HookOutputEntryKind::Error,
|
||||
text: "hook exited with code 7".to_string(),
|
||||
}],
|
||||
);
|
||||
failed_completed.visibility_hint = codex_app_server_protocol::HookVisibilityHint::Hidden;
|
||||
handle_hook_completed(&mut chat, failed_completed);
|
||||
let failed_history = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
let failed_snapshot = hook_live_and_history_snapshot(&chat, "failed", &failed_history);
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"visibility_hidden_hooks_skip_routine_rows_but_render_failures_snapshot",
|
||||
format!("{routine_running_snapshot}\n\n{routine_completed_snapshot}\n\n{failed_snapshot}")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn completed_hook_output_precedes_following_assistant_message() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -3165,6 +3219,7 @@ fn hook_run_summary(
|
||||
display_order: 0,
|
||||
status,
|
||||
status_message: status_message.map(str::to_string),
|
||||
visibility_hint: Default::default(),
|
||||
started_at: 1,
|
||||
completed_at: (status != codex_app_server_protocol::HookRunStatus::Running).then_some(2),
|
||||
duration_ms: (status != codex_app_server_protocol::HookRunStatus::Running).then_some(1),
|
||||
|
||||
@@ -952,6 +952,7 @@ approval_policy = "never"
|
||||
timeout_sec: Some(10),
|
||||
r#async: false,
|
||||
status_message: Some("checking".to_string()),
|
||||
visibility_hint: Default::default(),
|
||||
}],
|
||||
}],
|
||||
..Default::default()
|
||||
|
||||
@@ -106,6 +106,7 @@ const RAW_TOOL_OUTPUT_WIDTH: usize = 10_000;
|
||||
mod hook_cell;
|
||||
|
||||
pub(crate) use hook_cell::HookCell;
|
||||
pub(crate) use hook_cell::hook_run_should_skip_render;
|
||||
pub(crate) use hook_cell::new_active_hook_cell;
|
||||
pub(crate) use hook_cell::new_completed_hook_cell;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ use codex_app_server_protocol::HookOutputEntry;
|
||||
use codex_app_server_protocol::HookOutputEntryKind;
|
||||
use codex_app_server_protocol::HookRunStatus;
|
||||
use codex_app_server_protocol::HookRunSummary;
|
||||
use codex_app_server_protocol::HookVisibilityHint;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::widgets::Paragraph;
|
||||
@@ -679,6 +680,16 @@ pub(crate) fn new_completed_hook_cell(run: HookRunSummary, animations_enabled: b
|
||||
HookCell::new_completed(run, animations_enabled)
|
||||
}
|
||||
|
||||
/// Returns true when a hook marked as hidden has no user-relevant consequence to render.
|
||||
pub(crate) fn hook_run_should_skip_render(run: &HookRunSummary) -> bool {
|
||||
run.visibility_hint == HookVisibilityHint::Hidden
|
||||
&& match run.status {
|
||||
HookRunStatus::Running => true,
|
||||
HookRunStatus::Completed => run.entries.is_empty(),
|
||||
HookRunStatus::Blocked | HookRunStatus::Failed | HookRunStatus::Stopped => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true for hook completions that should be invisible in history.
|
||||
fn hook_run_is_quiet_success(run: &HookRunSummary) -> bool {
|
||||
run.status == HookRunStatus::Completed && run.entries.is_empty()
|
||||
@@ -802,6 +813,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_hook_skips_routine_running_and_empty_completion_rows() {
|
||||
let mut run = hook_run_summary("hook-1");
|
||||
run.visibility_hint = HookVisibilityHint::Hidden;
|
||||
assert!(hook_run_should_skip_render(&run));
|
||||
|
||||
run.status = HookRunStatus::Completed;
|
||||
assert!(hook_run_should_skip_render(&run));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_hook_still_surfaces_user_relevant_outcomes() {
|
||||
let mut run = hook_run_summary("hook-1");
|
||||
run.visibility_hint = HookVisibilityHint::Hidden;
|
||||
run.status = HookRunStatus::Failed;
|
||||
assert!(!hook_run_should_skip_render(&run));
|
||||
|
||||
run.status = HookRunStatus::Blocked;
|
||||
assert!(!hook_run_should_skip_render(&run));
|
||||
|
||||
run.status = HookRunStatus::Stopped;
|
||||
assert!(!hook_run_should_skip_render(&run));
|
||||
|
||||
run.status = HookRunStatus::Completed;
|
||||
run.entries = vec![HookOutputEntry {
|
||||
kind: HookOutputEntryKind::Warning,
|
||||
text: "Review this hook result".to_string(),
|
||||
}];
|
||||
assert!(!hook_run_should_skip_render(&run));
|
||||
}
|
||||
|
||||
fn hook_run_summary(id: &str) -> HookRunSummary {
|
||||
HookRunSummary {
|
||||
id: id.to_string(),
|
||||
@@ -814,6 +856,7 @@ mod tests {
|
||||
display_order: 0,
|
||||
status: HookRunStatus::Running,
|
||||
status_message: Some("checking output policy".to_string()),
|
||||
visibility_hint: HookVisibilityHint::Default,
|
||||
started_at: 1,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
|
||||
Reference in New Issue
Block a user