mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Show plugin hooks in plugin details (#21447)
Supersedes the abandoned #19859, rebuilt on latest `main`. # Why PR #19705 adds discovery for hooks bundled with plugins, but `/plugins` still only shows skills, apps, and MCP servers. This follow-up makes bundled hooks visible in the same plugin detail view so users can inspect the full plugin surface in one place. We also need `PluginHookSummary` to populate Plugin Hooks in the app; `hooks/list` is not enough there because plugin detail needs to show hooks for disabled plugins too. # What - extend `plugin/read` with `PluginHookSummary` entries for bundled hooks - summarize plugin hooks while loading plugin details - render a `Hooks` row in the `/plugins` detail popup <img width="3456" height="848" alt="CleanShot 2026-04-27 at 11 45 34@2x" src="https://github.com/user-attachments/assets/fe3a38d6-a260-4351-8513-fb04c93d725b" />
This commit is contained in:
committed by
GitHub
Unverified
parent
898f5bfeaa
commit
40e282849c
@@ -6,6 +6,7 @@ use crate::loader::configured_curated_plugin_ids_from_codex_home;
|
||||
use crate::loader::curated_plugin_cache_version;
|
||||
use crate::loader::installed_plugin_telemetry_metadata;
|
||||
use crate::loader::load_plugin_apps;
|
||||
use crate::loader::load_plugin_hooks;
|
||||
use crate::loader::load_plugin_mcp_servers;
|
||||
use crate::loader::load_plugin_skills;
|
||||
use crate::loader::load_plugins_from_layer_stack;
|
||||
@@ -54,6 +55,7 @@ use codex_config::set_user_plugin_enabled;
|
||||
use codex_config::types::PluginConfig;
|
||||
use codex_config::version_for_toml;
|
||||
use codex_core_skills::SkillMetadata;
|
||||
use codex_hooks::plugin_hook_declarations;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_plugin::AppConnectorId;
|
||||
@@ -61,6 +63,7 @@ use codex_plugin::PluginCapabilitySummary;
|
||||
use codex_plugin::PluginId;
|
||||
use codex_plugin::PluginIdError;
|
||||
use codex_plugin::prompt_safe_plugin_description;
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
@@ -228,11 +231,18 @@ pub struct PluginDetail {
|
||||
pub enabled: bool,
|
||||
pub skills: Vec<SkillMetadata>,
|
||||
pub disabled_skill_paths: HashSet<AbsolutePathBuf>,
|
||||
pub hooks: Vec<PluginHookSummary>,
|
||||
pub apps: Vec<AppConnectorId>,
|
||||
pub mcp_server_names: Vec<String>,
|
||||
pub details_unavailable_reason: Option<PluginDetailsUnavailableReason>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PluginHookSummary {
|
||||
pub key: String,
|
||||
pub event_name: HookEventName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PluginDetailsUnavailableReason {
|
||||
InstallRequiredForRemoteSource,
|
||||
@@ -1300,6 +1310,7 @@ impl PluginsManager {
|
||||
enabled: plugin.enabled,
|
||||
skills: Vec::new(),
|
||||
disabled_skill_paths: HashSet::new(),
|
||||
hooks: Vec::new(),
|
||||
apps: Vec::new(),
|
||||
mcp_server_names: Vec::new(),
|
||||
details_unavailable_reason: Some(
|
||||
@@ -1357,6 +1368,20 @@ impl PluginsManager {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let hooks = if config.plugin_hooks_enabled {
|
||||
let plugin_data_root = self.store.plugin_data_root(&plugin_id);
|
||||
let (hook_sources, _hook_load_warnings) =
|
||||
load_plugin_hooks(&source_path, &plugin_id, &plugin_data_root, &manifest.paths);
|
||||
plugin_hook_declarations(&hook_sources)
|
||||
.into_iter()
|
||||
.map(|hook| PluginHookSummary {
|
||||
key: hook.key,
|
||||
event_name: hook.event_name,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let apps = load_plugin_apps(source_path.as_path()).await;
|
||||
let mut mcp_server_names = load_plugin_mcp_servers(source_path.as_path())
|
||||
.await
|
||||
@@ -1377,6 +1402,7 @@ impl PluginsManager {
|
||||
enabled: plugin.enabled,
|
||||
skills: resolved_skills.skills,
|
||||
disabled_skill_paths: resolved_skills.disabled_skill_paths,
|
||||
hooks,
|
||||
apps,
|
||||
mcp_server_names,
|
||||
details_unavailable_reason: None,
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_config::McpServerConfig;
|
||||
use codex_config::McpServerToolConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -1933,13 +1934,48 @@ async fn read_plugin_for_config_installed_git_source_reads_from_cache_without_cl
|
||||
&cached_plugin_root.join(".mcp.json"),
|
||||
r#"{"mcpServers":{"toolkit":{"command":"toolkit-mcp"}}}"#,
|
||||
);
|
||||
write_file(
|
||||
&cached_plugin_root.join("hooks/hooks.json"),
|
||||
r#"{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo startup"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo first"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo second"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
plugin_hooks = true
|
||||
|
||||
[plugins."toolkit@debug"]
|
||||
enabled = true
|
||||
|
||||
[hooks.state."toolkit@debug:hooks/hooks.json:pre_tool_use:0:0"]
|
||||
enabled = false
|
||||
"#,
|
||||
);
|
||||
|
||||
@@ -1978,6 +2014,23 @@ enabled = true
|
||||
outcome.plugin.apps,
|
||||
vec![AppConnectorId("connector_calendar".to_string())]
|
||||
);
|
||||
assert_eq!(
|
||||
outcome.plugin.hooks,
|
||||
vec![
|
||||
PluginHookSummary {
|
||||
key: "toolkit@debug:hooks/hooks.json:pre_tool_use:0:0".to_string(),
|
||||
event_name: HookEventName::PreToolUse,
|
||||
},
|
||||
PluginHookSummary {
|
||||
key: "toolkit@debug:hooks/hooks.json:pre_tool_use:0:1".to_string(),
|
||||
event_name: HookEventName::PreToolUse,
|
||||
},
|
||||
PluginHookSummary {
|
||||
key: "toolkit@debug:hooks/hooks.json:session_start:0:0".to_string(),
|
||||
event_name: HookEventName::SessionStart,
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(outcome.plugin.mcp_server_names, vec!["toolkit".to_string()]);
|
||||
assert!(
|
||||
!tmp.path()
|
||||
|
||||
Reference in New Issue
Block a user