Files
codex/codex-rs/tui/src/hooks_rpc.rs
T
andrewgu-oai c483edf177 Include environment hooks in thread hook listings (#56553)
## Why

Listing hooks by working directory alone omits hooks supplied by a thread's selected execution environments, including hooks that can block a turn.

## What changed

- Add `threadId` to `hooks/list` to return a loaded thread's current hook inventory, including selected, ready environments. Reject requests that also specify `cwds`.
- Expose `environmentId` in hook metadata and have the TUI hooks browser query the current thread and label environment-owned hooks. Keep working-directory lookup for startup review.
- Represent environment hook source paths with `PathUri` and use `LegacyAppPathString` in hook run summaries to preserve native path conventions.

## Testing

Add coverage for listing a blocking environment hook and removing it after environment failure, delivering prompt input and reporting exit status in PowerShell and Bash, and running `PreToolUse` hooks only on the command's owning executor. Extend remote network tests to verify that environment hooks use the command's network policy while retaining their filesystem access.

GitOrigin-RevId: 97229e22793df51f4bd502c1f9e19f0c52fb2305
2026-10-23 18:39:28 +00:00

107 lines
3.5 KiB
Rust

use codex_app_server_client::AppServerRequestHandle;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigWriteResponse;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookTrustStatus;
use codex_app_server_protocol::HooksListEntry;
use codex_app_server_protocol::HooksListParams;
use codex_app_server_protocol::HooksListResponse;
use codex_app_server_protocol::MergeStrategy;
use codex_app_server_protocol::RequestId;
use codex_protocol::ThreadId;
use color_eyre::eyre::Result;
use color_eyre::eyre::WrapErr;
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct HookTrustUpdate {
pub(crate) key: String,
pub(crate) current_hash: String,
}
/// A running thread's inventory, or configuration to inspect before a thread exists.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum HooksListTarget {
Thread(ThreadId),
Cwd(PathBuf),
}
pub(crate) async fn fetch_hooks_list(
request_handle: AppServerRequestHandle,
target: &HooksListTarget,
) -> Result<HooksListEntry> {
let params = match target {
HooksListTarget::Thread(id) => HooksListParams {
thread_id: Some(id.to_string()),
..Default::default()
},
HooksListTarget::Cwd(cwd) => HooksListParams {
cwds: vec![cwd.clone()],
..Default::default()
},
};
let request_id = RequestId::String(format!("hooks-list-{}", Uuid::new_v4()));
let response: HooksListResponse = request_handle
.request_typed(ClientRequest::HooksList { request_id, params })
.await
.wrap_err("hooks/list failed in TUI")?;
response
.data
.into_iter()
.next()
.ok_or_else(|| color_eyre::eyre::eyre!("hooks/list returned no inventory for {target:?}"))
}
pub(crate) fn hook_needs_review(hook: &HookMetadata) -> bool {
matches!(
hook.trust_status,
HookTrustStatus::Untrusted | HookTrustStatus::Modified
)
}
pub(crate) async fn write_hook_trusts(
request_handle: AppServerRequestHandle,
trust_updates: Vec<HookTrustUpdate>,
) -> Result<ConfigWriteResponse> {
let request_id = RequestId::String(format!("hooks-config-write-{}", Uuid::new_v4()));
let value = serde_json::Value::Object(
trust_updates
.into_iter()
.map(|update| {
(
update.key,
serde_json::json!({
"trusted_hash": update.current_hash,
}),
)
})
.collect(),
);
request_handle
.request_typed(ClientRequest::ConfigBatchWrite {
request_id,
params: ConfigBatchWriteParams {
edits: vec![codex_app_server_protocol::ConfigEdit {
key_path: "hooks.state".to_string(),
value,
merge_strategy: MergeStrategy::Upsert,
}],
file_path: None,
expected_version: None,
reload_user_config: true,
},
})
.await
.wrap_err("config/batchWrite failed while updating hook trust in TUI")
}
pub(crate) async fn write_hook_trust(
request_handle: AppServerRequestHandle,
key: String,
current_hash: String,
) -> Result<ConfigWriteResponse> {
write_hook_trusts(request_handle, vec![HookTrustUpdate { key, current_hash }]).await
}