Improve hooks trust flow in TUI (#21755)

# Why
Hooks that need trust review were easy to miss, and the existing TUI
flow made users discover `/hooks` manually before they could decide
whether to inspect or trust them.

# What
- add a startup review prompt for new or changed hooks before normal
composer use
- add a top-level `t` shortcut in `/hooks` to trust every review-needed
hook at once
- make pending-review rows and helper copy use warning styling

## TUI

### Startup review interstitial

```text
Hooks need review
2 hooks are new or changed.
Hooks can run outside the sandbox after you trust them.

› 1. Review hooks
  2. Trust all and continue
  3. Continue without trusting (hooks won't run)
```

### Top-level `/hooks` page when review is needed

```text
Hooks
Lifecycle hooks from config and enabled plugins.

⚠ 1 hook needs review before it can run.

Event                 Installed   Active   Review   Description
PreToolUse            1           0        1        Before a tool executes
...

Press t to trust all; enter to review hooks; esc to close
```
This commit is contained in:
Abhinav authored and GitHub committed 2026-05-09 21:17:30 +00:00
1 parent 53468b97f6
commit 6d747db7d8
16 files changed
+846 -209

No files matched your search

+12 -13
View File
@@ -3,6 +3,8 @@ use std::path::PathBuf;
use super::ChatWidget;
use crate::app_event::AppEvent;
use crate::bottom_pane::HooksBrowserView;
use crate::hooks_rpc::hooks_list_entry_for_cwd;
use codex_app_server_protocol::HooksListEntry;
use codex_app_server_protocol::HooksListResponse;
impl ChatWidget {
@@ -23,21 +25,18 @@ impl ChatWidget {
match result {
Ok(response) => {
let (hooks, warnings, errors) = response
.data
.into_iter()
.find(|entry| entry.cwd.as_path() == cwd.as_path())
.map(|entry| (entry.hooks, entry.warnings, entry.errors))
.unwrap_or_default();
self.bottom_pane.show_view(Box::new(HooksBrowserView::new(
hooks,
warnings,
errors,
self.app_event_tx.clone(),
)));
self.request_redraw();
self.open_hooks_browser(hooks_list_entry_for_cwd(response, &cwd));
}
Err(err) => self.add_error_message(format!("Failed to load hooks: {err}")),
}
}
pub(crate) fn open_hooks_browser(&mut self, entry: HooksListEntry) {
self.bottom_pane
.show_view(Box::new(HooksBrowserView::from_entry(
entry,
self.app_event_tx.clone(),
)));
self.request_redraw();
}
}