mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
## Why `hooks/list` and `hooks/config/write` give us read/write access to hooks and their state. This hooks up the TUI as a client so users can inspect and manage that state directly. ## What - add a two-page `/hooks` browser in the TUI: an event overview with installed/active counts, followed by a per-event handler page with toggle controls and detail rendering - thread managed-state metadata through hook discovery and `hooks/list` so the UI can label admin-managed hooks and suppress toggles for them - persist hook toggles through the existing config-write path and add snapshot coverage for the event list, handler list, managed-hook, and empty states ## Stack 1. openai/codex#19705 2. openai/codex#19778 3. openai/codex#19840 4. This PR - openai/codex#19882 ## Reviewer Notes - Main UI logic is in `codex-rs/tui/src/bottom_pane/hooks_browser_view.rs`; most of the diff is the new view plus its snapshot coverage - Request / write plumbing for opening the browser and persisting toggles is in `codex-rs/tui/src/app/background_requests.rs` and `codex-rs/tui/src/chatwidget/hooks.rs` - Outside the TUI, the only behavioral change in this PR is threading `is_managed` through hook discovery and `hooks/list` so managed hooks render as non-toggleable - The `codex-rs/tui/src/status/snapshots/` churn is unrelated merge fallout from the stacked base branch's newer permission-label rendering --------- Co-authored-by: Codex <noreply@openai.com>
95 lines
3.8 KiB
Rust
95 lines
3.8 KiB
Rust
//! Shared App fixtures for app submodule unit tests.
|
|
//!
|
|
//! This module keeps heavyweight `App` construction and config-inspection helpers available to
|
|
//! focused sibling test modules without making `app/tests.rs` the only practical place to test
|
|
//! app-owned behavior.
|
|
|
|
use super::*;
|
|
use crate::chatwidget::tests::make_chatwidget_manual_with_sender;
|
|
|
|
pub(super) async fn make_test_app() -> App {
|
|
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await;
|
|
let config = chat_widget.config_ref().clone();
|
|
let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone());
|
|
let model = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
|
let session_telemetry = test_session_telemetry(&config, model.as_str());
|
|
|
|
App {
|
|
model_catalog: chat_widget.model_catalog(),
|
|
session_telemetry,
|
|
app_event_tx,
|
|
chat_widget,
|
|
config,
|
|
active_profile: None,
|
|
cli_kv_overrides: Vec::new(),
|
|
harness_overrides: ConfigOverrides::default(),
|
|
runtime_approval_policy_override: None,
|
|
runtime_permission_profile_override: None,
|
|
file_search,
|
|
transcript_cells: Vec::new(),
|
|
overlay: None,
|
|
deferred_history_lines: Vec::new(),
|
|
has_emitted_history_lines: false,
|
|
transcript_reflow: TranscriptReflowState::default(),
|
|
initial_history_replay_buffer: None,
|
|
enhanced_keys_supported: false,
|
|
keymap: crate::keymap::RuntimeKeymap::defaults(),
|
|
commit_anim_running: Arc::new(AtomicBool::new(false)),
|
|
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
|
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
|
|
backtrack: BacktrackState::default(),
|
|
backtrack_render_pending: false,
|
|
feedback: codex_feedback::CodexFeedback::new(),
|
|
feedback_audience: FeedbackAudience::External,
|
|
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
|
remote_app_server_url: None,
|
|
remote_app_server_auth_token: None,
|
|
pending_update_action: None,
|
|
pending_shutdown_exit_thread_id: None,
|
|
windows_sandbox: WindowsSandboxState::default(),
|
|
thread_event_channels: HashMap::new(),
|
|
thread_event_listener_tasks: HashMap::new(),
|
|
agent_navigation: AgentNavigationState::default(),
|
|
side_threads: HashMap::new(),
|
|
active_thread_id: None,
|
|
active_thread_rx: None,
|
|
primary_thread_id: None,
|
|
last_subagent_backfill_attempt: None,
|
|
primary_session_configured: None,
|
|
pending_primary_events: VecDeque::new(),
|
|
pending_app_server_requests: PendingAppServerRequests::default(),
|
|
pending_plugin_enabled_writes: HashMap::new(),
|
|
pending_hook_enabled_writes: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry {
|
|
let model_info = crate::legacy_core::test_support::construct_model_info_offline(model, config);
|
|
SessionTelemetry::new(
|
|
ThreadId::new(),
|
|
model,
|
|
model_info.slug.as_str(),
|
|
/*account_id*/ None,
|
|
/*account_email*/ None,
|
|
/*auth_mode*/ None,
|
|
"test_originator".to_string(),
|
|
/*log_user_prompts*/ false,
|
|
"test".to_string(),
|
|
serde_json::from_value(serde_json::json!("cli"))
|
|
.expect("cli session source should deserialize"),
|
|
)
|
|
}
|
|
|
|
pub(super) fn app_enabled_in_effective_config(config: &Config, app_id: &str) -> Option<bool> {
|
|
config
|
|
.config_layer_stack
|
|
.effective_config()
|
|
.as_table()
|
|
.and_then(|table| table.get("apps"))
|
|
.and_then(TomlValue::as_table)
|
|
.and_then(|apps| apps.get(app_id))
|
|
.and_then(TomlValue::as_table)
|
|
.and_then(|app| app.get("enabled"))
|
|
.and_then(TomlValue::as_bool)
|
|
}
|