feat: plugin / MCP / IDE management — editable UX + hot refresh (#44 #47 #49 #41) - #87
Merged
Crsei merged 5 commits intoApr 21, 2026
Conversation
…rsei#47 Crsei#49 Crsei#41) Shared infrastructure for the plugin/MCP/IDE management epic: - IPC protocol: add McpCommand::{QueryConfig, UpsertConfig, RemoveConfig} + McpEvent::{ConfigList, ConfigChanged, ConfigError} so /mcp can round-trip editable entries distinct from runtime status. Add PluginCommand::{Reload, Uninstall} + PluginEvent::{RefreshNeeded, Reloaded} for hot-refresh. Introduce IdeCommand + IdeEvent + IdeInfo as a fresh subsystem. - plugins::refresh: extract reload_plugins() primitive that wraps clear_plugins + init_plugins and emits PluginEvent::Reloaded on the bus, with a ReloadReport surfacing per-plugin error state. Wire it through PluginCommand::Reload in the IPC handler. - ServerListEditor: generic React/opentui widget for list + cursor + action shortcuts + optional edit slot. Shared by /mcp, /plugin, /ide. Scope: foundation only. Each of the four slash commands remains a text-oriented stub; Team A-D pick up the feature work in follow-ups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the foundation primitive plugins::reload_plugins() in a new slash
command handler. The command emits "Reloaded {count} plugin(s) in
{duration_ms}ms." and appends one error line per failing plugin when
ReloadReport::errors is non-empty.
Register the command next to /plugin in the command registry and add
it to the TS autocomplete catalog so tab-completion picks it up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges Team D's /ide work on top of the foundation commit, reusing the IPC types (IdeCommand, IdeEvent, IdeInfo, ConfigScope::Ide) landed in 2f23599 instead of Team D's parallel duplicates. - New `ide` module (crates/claude-code-rs/src/ide/mod.rs): detect_ides() via PATH + TERM_PROGRAM heuristics for vscode, cursor, and JetBrains; select_ide / clear_selection / selected_ide persisted under `selectedIde` in {data_root}/settings.json; ide_mcp_config builds a stdio bridge config; emits Ide events via EVENT_TX. - New `/ide` slash command (commands/ide_cmd.rs): detect, status, select, clear, reconnect subcommands. - cc-mcp::discovery: new set_ide_hook mirrors set_plugin_hook; the host registers `ide::selected_ide_mcp_config` so the selected IDE's bridge merges between plugins and user settings. - Real handle_ide_command wired to crate::ide::{select_ide, clear_selection, reconnect_selected}; build_ide_info_list now calls crate::ide::detect_ides. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges Team A's /mcp work on top of the foundation commit, reusing the ConfigScope/McpServerConfigEntry types landed in 2f23599 and reconciling with Team D's IDE hook. - cc-mcp::discovery: scope-aware discovery via ScopedMcpServer + DiscoveryScope. New set_scoped_plugin_hook preserves the owning plugin id; IDE hook (issue Crsei#41) sits between plugins and user settings so user settings can still override. - plugins: new discover_plugin_mcp_servers_scoped returns (plugin_id, config) pairs; legacy discover_plugin_mcp_servers delegates to it. - /mcp command rewrite (commands/mcp_cmd.rs): list groups by scope with live status, add/edit accept --command/--arg/--env/--url/ --transport/--scope/--browser, remove auto-picks the matching editable scope (or asks with --scope when ambiguous), connect/disconnect/reconnect queue runtime lifecycle ops. - handle_mcp_command QueryConfig/UpsertConfig/RemoveConfig handlers land with real persistence (user → {data_root}/settings.json, project → {cwd}/.cc-rust/settings.json), editable-scope guard, and ConfigError on validation/IO failure. - build_mcp_server_config_entries attributes each entry to its real origin scope (not all User as in the foundation stub). - ConfigScope::label() helper for the /mcp list grouping output. Kept from foundation / Team D: ConfigError field name (`error`), PluginEvent::{RefreshNeeded, Reloaded}, IdeEvent, Plugin::{Reload, Uninstall} handlers, handle_ide_command, build_ide_info_list. Tests: handler-level upsert/remove round-trips (user/project), scope rejection for read-only Plugin/Ide, slash-command add/edit/remove/ ambiguity, scoped-discovery precedence. All env-mutating tests are #[serial_test::serial]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
) Merges Team B's /plugin work on top of the foundation commit, keeping the foundation's PluginEvent::Reloaded { count, had_error } shape and ReloadReport (used by Team C's /reload-plugins) rather than Team B's delta-oriented variant. - /plugin command rewrite (commands/plugin_cmd.rs): layered table view with install/enabled/active columns; new subcommands list/installed/disabled/errors/status/enable/disable/ uninstall [--purge]/help; drift-aware post-action hints emit PluginEvent::RefreshNeeded via plugins::emit_event_external. - plugins::mod: new uninstall_plugin(id, purge_cache), needs_refresh(), compute_drift() with DriftReport, status_variant_differs(), cache_path_for() (handles `cache/{mp}/{name}/{version}` and fallback layouts), and emit_event_external(). - Cache purge removes the plugin-level directory (one level above the version subdir) so --purge wipes every cached version. - handle_plugin_command::Uninstall now calls uninstall_plugin and emits PluginEvent::StatusChanged { status: "not_installed" }; absent plugin returns a SystemInfo. - Existing registry-touching tests marked #[serial_test::serial] so the new serial CC_RUST_HOME tests don't race with them. Drift semantics: ids added/removed on disk, or PluginStatus variant mismatch, counts as drift. Error-to-error status shifts are NOT drift (messages can fluctuate on transient conditions; the existing StatusChanged event carries the new message). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yaohaowei0914
pushed a commit
to yaohaowei0914/claude-code-rust
that referenced
this pull request
Apr 21, 2026
Integrates the fork-agent infrastructure and ancillary command/IPC updates that landed on rust-lite after this branch opened: - PR Crsei#85: fork-agent infra + /btw /simplify /advisor (Crsei#33 Crsei#37 Crsei#62) - PR Crsei#87: MCP/plugin/reload-plugins/ide/reload-plugins commands (Crsei#41 Crsei#44 Crsei#47 Crsei#49) - PR Crsei#88: /loop /schedule /team-onboarding (Crsei#43 Crsei#58 Crsei#60 Crsei#63) ## Conflict resolution Git's recursive merge handled every hunk on its own — each of the five files flagged as overlapping had non-overlapping line ranges between my workspace-split edits and rust-lite's feature edits: - `crates/cc-engine/src/types/app_state.rs` — my move to cc-engine vs. advisor/fork-related AppState fields. Clean auto-merge. - `crates/claude-code-rs/src/engine/agent/mod.rs` — my `cc_types::agent_*` import rewrites vs. rust-lite's `pub mod fork;` addition. Clean. - `crates/claude-code-rs/src/engine/lifecycle/deps.rs` — my `cc_types::background_agents` retyping and `hook_runner()` impl vs. rust-lite's `advisor_model` stripping logic. Clean. - `crates/claude-code-rs/src/query/deps.rs` — my `hook_runner()` / `drain_background_results` signature changes vs. rust-lite's new `ModelCallParams::advisor_model` field. Clean. - `crates/claude-code-rs/src/query/loop_impl.rs` — my hook-runner-trait rewiring vs. rust-lite's advisor model plumbing. Clean. All 40+ other touched files (api/*, commands/*, ide/*, ipc/*, services/*, etc.) merged without conflict — they're in regions my branch didn't touch. ## Verification - `cargo check --workspace`: clean (2 pre-existing warnings). - `cargo test -p cc-types`: 4/4 passing (teams + background_agents). - `cargo test -p cc-engine`: status_line + types suite unchanged. - `cargo test --bin claude-code-rs engine::agent query::loop_impl`: 69/69 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships the four epic-related commands as a coordinated set so they share one "server list + enable/disable + edit config" foundation:
/mcpeditable UX — add/edit/remove across user/project scopes, scope-aware discovery, real persistence tosettings.json./pluginaligned with layered state — install/enable/active columns,uninstall [--purge], drift-awareRefreshNeededhints./reload-pluginshot refresh — wraps the newreload_plugins()primitive; reports count + error breakdown./ideMCP-backed integration — detect vscode/cursor/JetBrains, persist selection, merge IDE MCP bridge into discovery between plugins and user settings.What changed (by commit)
2f23599foundation — IPC protocol extensions (McpCommand::{QueryConfig, UpsertConfig, RemoveConfig},McpEvent::{ConfigList, ConfigChanged, ConfigError},PluginCommand::{Reload, Uninstall},PluginEvent::{RefreshNeeded, Reloaded}, newIdeCommand/IdeEvent,ConfigScope,McpServerConfigEntry,IdeInfo),reload_plugins()primitive inplugins/refresh.rswithReloadReport, sharedServerListEditor.tsxReact component.2deb29c/reload-plugins(Team C) — newreload_plugins_cmd.rs, registered incommands/mod.rs, output formatReloaded N plugin(s) in Mmswith per-error lines.0596364/ide(Team D) — newide/mod.rs(PATH +TERM_PROGRAMheuristics,selectedIdepersistence, stdio MCP bridge config), newide_cmd.rs,cc-mcp::discovery::set_ide_hookmerges the IDE's bridge between plugins and user settings.ea0301f/mcpeditable (Team A) — full command rewrite withlist/status/add/edit/remove/connect/disconnect/reconnect, scope-aware discovery viaScopedMcpServer+DiscoveryScope,build_mcp_server_config_entriesattributes real origin scope, handler persists to{data_root}/settings.jsonor{cwd}/.cc-rust/settings.json, read-only scope rejection.2506501/pluginlayered (Team B) — command rewrite withinstalled/disabled/errors/status/uninstall [--purge], newuninstall_plugin()+needs_refresh()+compute_drift()inplugins/mod.rs, handler wiresPluginCommand::Uninstallto emitStatusChanged { status: "not_installed" }.Reconciliation notes
Three of the four parallel teams (A, B, D) started from a worktree that didn't include the foundation commit, so each independently rebuilt portions of the IPC types. The merge keeps the foundation's shape as canonical:
PluginEvent::Reloaded { count, had_error }(not B's{count, added, removed, updated}delta variant).McpEvent::ConfigError { server_name, error }(not A'smessage).ConfigScope::User(not D'sGlobal).Team A's
DiscoveryScope::Ide(String)and Team D'sset_ide_hookcoexist: IDE configs inject via the hook and get tagged withIdescope at discovery time.Test plan
cargo build -p claude-code-rs— clean (0 new warnings; only 2 pre-existingweb/handlers.rsdead_code).cargo test -p claude-code-rs --bin claude-code-rs -- --test-threads=1— 1352/1352 pass.cargo test -p cc-mcp— 27/27 pass.cargo test ipc::subsystem plugins::refresh commands::mcp_cmd commands::plugin_cmd commands::reload_plugins_cmd commands::ide_cmd ide— all targeted tests green.config_cmd,daemon,teams,tools::worktree). Serial-test run is green.Not in scope (follow-ups)
ServerListEditorfor/mcp,/plugin,/ide. The component ships with the foundation and the TS protocol types are all mirrored; only the view-layer glue is deferred./ide reconnect(currently schedules viaConnectionStateChanged; the MCP manager picks up on next discovery pass).running(currently env-only; would needsysinfo)./reload-plugins—QueryEnginekeeps a long-livedtoolssnapshot; the existing/plugin enable|disablepath has the same limitation and this PR matches that behavior. Documented inreload_plugins_cmd.rs.🤖 Generated with Claude Code