feat: add honest plugin inspection reporting

Shift the Rust parity increment away from implying TS-style plugin UX and toward an honest inspection surface. /plugin now reports current local plugin support, checked directories, and missing runtime wiring, while /reload-plugins rebuilds the runtime and prints the same inspection snapshot.\n\nConstraint: Rust only supports local manifest-backed plugins today; marketplace/discovery parity does not exist\nRejected: Stub marketplace installer flow | would overstate current capability\nRejected: Keep /plugin as list-only output | hides important gaps and checked paths\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep plugin reporting aligned with actual runtime wiring; do not advertise manifest commands/hooks as active until the runtime uses them\nTested: cargo test -p commands\nTested: cargo test -p claw-cli\nNot-tested: cargo clippy -p commands -p claw-cli --tests -- -D warnings (blocked by pre-existing workspace warnings in commands/claw-cli/lsp)
This commit is contained in:
Yeachan-Heo
2026-04-02 00:04:23 +00:00
parent a2f22b1ece
commit b8d78c9a53
4 changed files with 240 additions and 19 deletions

View File

@@ -23,8 +23,8 @@ use api::{
use commands::{
handle_agents_slash_command, handle_hooks_slash_command, handle_plugins_slash_command,
handle_skills_slash_command, render_slash_command_help, resume_supported_slash_commands,
slash_command_specs, suggest_slash_commands, SlashCommand,
handle_skills_slash_command, render_plugin_inspection_report, render_slash_command_help,
resume_supported_slash_commands, slash_command_specs, suggest_slash_commands, SlashCommand,
};
use compat_harness::{extract_manifest, UpstreamPaths};
use init::initialize_repo;
@@ -1015,6 +1015,7 @@ fn run_resume_command(
| SlashCommand::Permissions { .. }
| SlashCommand::Session { .. }
| SlashCommand::Plugins { .. }
| SlashCommand::ReloadPlugins
| SlashCommand::Unknown(_) => Err("unsupported resumed slash command".into()),
}
}
@@ -1340,6 +1341,7 @@ impl LiveCli {
SlashCommand::Plugins { action, target } => {
self.handle_plugins_command(action.as_deref(), target.as_deref())?
}
SlashCommand::ReloadPlugins => self.reload_plugins_command()?,
SlashCommand::Agents { args } => {
Self::print_agents(args.as_deref())?;
false
@@ -1671,6 +1673,22 @@ impl LiveCli {
Ok(false)
}
fn reload_plugins_command(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
self.reload_runtime_features()?;
let cwd = env::current_dir()?;
let loader = ConfigLoader::default_for(&cwd);
let runtime_config = loader.load()?;
let manager = build_plugin_manager(&cwd, &loader, &runtime_config);
let inspection = manager.inspect()?;
println!(
"Plugin runtime reloaded from local manifests.\n{}",
render_plugin_inspection_report(&inspection)
);
Ok(false)
}
fn reload_runtime_features(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.runtime = build_runtime(
self.runtime.session().clone(),
@@ -4528,8 +4546,9 @@ mod tests {
assert!(help.contains("/export [file]"));
assert!(help.contains("/session [list|switch <session-id>]"));
assert!(help.contains(
"/plugin [list|install <path>|enable <name>|disable <name>|uninstall <id>|update <id>]"
"/plugin [inspect|list|install <path>|enable <name>|disable <name>|uninstall <id>|update <id>]"
));
assert!(help.contains("/reload-plugins"));
assert!(help.contains("aliases: /plugins, /marketplace"));
assert!(help.contains("/agents"));
assert!(help.contains("/skills"));
@@ -4556,11 +4575,20 @@ mod tests {
.expect("plugin descriptor should exist");
assert_eq!(
plugin.description.as_deref(),
Some("Manage Claw Code plugins")
Some("Inspect and manage local Claw Code plugins")
);
assert!(plugin.aliases.contains(&"/plugins".to_string()));
assert!(plugin.aliases.contains(&"/marketplace".to_string()));
let reload = descriptors
.iter()
.find(|descriptor| descriptor.command == "/reload-plugins")
.expect("reload plugins descriptor should exist");
assert_eq!(
reload.description.as_deref(),
Some("Reload plugin-derived runtime features and print current support")
);
let exit = descriptors
.iter()
.find(|descriptor| descriptor.command == "/exit")