feat(extensions): cross-agent install to project scope - #102
Merged
Conversation
- windsurf: remove `.windsurf/mcp_config.json` from both project_mcp_config_relpath and project_settings_patterns — the official MCP doc documents only the global ~/.codeium/windsurf/mcp_config.json, unlike the skills/hooks docs on the same site which do scope workspace paths; add a pinning test so the deliberate None survives - cursor: drop the unsourced "2.4+" version claim from the skills comment - antigravity: cite the official skills codelab as the primary source and note the singular `.agent/skills/` is still live for the CLI Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
Add a required target_scope param to service::install_to_agent and make all four kind branches resolve their write path through the existing *_for(scope) adapter methods: - skill/CLI: skill_dir_for(target_scope) (+ category variant for hermes) - MCP: mcp_config_path_for(target_scope), clean Validation error for agents without project-level MCP (windsurf, antigravity, hermes) - hook: hook_config_path_for(target_scope); the Kiro global-hook bail-out (kirodotdev/Kiro#5440) now applies only at Global scope — project-scope hook install to Kiro is exactly the supported alternative Reject installs into unregistered projects up front: sync_extensions prunes extension rows whose project path is missing from the projects table, so such an install would silently vanish on the next rescan. Delete ensure_codex_hooks_enabled (+ its tests): per official Codex docs hooks are enabled by default and [features].hooks is a disable switch, so writing `true` was redundant and would trample an explicit user opt-out. Hook execution is gated by project trust + /hooks review. Both endpoints pass &ConfigScope::Global as a placeholder until the next commit threads the request param through; Global behavior is unchanged (skill_dir_for(&Global) == skill_dirs().first(), *_config_path_for(&Global) == the v1 getters). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
Add a required target_scope field to the web InstallToAgentParams and the desktop Tauri command, threading it into the scope-aware service call. Required rather than defaulted, matching every sibling install endpoint: an implicit Global default could silently misroute a project-scope install from an out-of-date client. The web handler now returns a scope-correct extension ID via stable_id_with_scope_for (the value is currently discarded by the frontend; the pre-existing hook translated-event ID caveat is documented in place). Tests: missing target_scope is a client error; both ConfigScope wire shapes deserialize. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
Derive per-agent install capabilities from adapter declarations
(AgentCapabilities::from_adapter) and ship them inside every AgentInfo
response from both the web and desktop list_agents:
- project_install.{skill,mcp,hook,cli}: whether the adapter declares a
project-level write path for the kind (cli follows skill — a CLI
install deploys the companion skill; the binary itself is global)
- hooks_supported: hook_format() != HookFormat::None
- global_hook_install: supports_global_hook_install() (false only for
Kiro, kirodotdev/Kiro#5440)
This makes the backend the single source of truth for the frontend's
per-(agent, kind, scope) install gating; the hand-maintained (and
already drifted) PROJECT_INSTALL_SUPPORT table in agent-capabilities.ts
is deleted in the follow-up frontend commit.
test_agent_capabilities_matrix pins the full 10-agent matrix, every
value doc-verified (citations in the plan doc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
Install to Agent now targets the active scope: the selected project in project mode, Global in Global and All modes (All shows an "installs to Global" hint instead of adding a picker). The source instance prefers the copy already in the target scope, then a global copy, then any — which also unblocks installing a project-only group back to Global (recreate a deleted global row). Gating now reads the backend-derived AgentInfo.capabilities everywhere: the hand-maintained PROJECT_INSTALL_SUPPORT table (already drifted from the Rust adapters) and the parallel AGENTS_WITHOUT_HOOKS / Kiro special-cases are deleted. Kiro hook installs grey out only when the target scope is Global (kirodotdev/Kiro#5440); project-scope Kiro hook install is now live. Project-incapable combos (windsurf MCP, hermes, antigravity MCP) disable with an explanatory tooltip. CLI bundles skip children the target agent can't take at the chosen scope (with an info toast) instead of failing mid-loop with a partial install; the Hermes category picker closes on scope switch so a pending install can't silently retarget. New pure helpers resolveInstallTargetScope / pickSourceInstance in extension-helpers (unit-tested); canInstallAtScope reads capabilities and its tests use AgentInfo fixtures; en/zh strings updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
Group agent badges (table column + detail pills) and the list's agent filter previously showed the cross-scope union, so a project view could claim an agent has a copy there when it only exists globally — and the filter could surface a card whose visible badges didn't contain the filtered agent. Both now go through a single agentsInScope projection (All mode keeps the full union); group identity stays scope-free. Also carry the active scope in the audit page's "View Extension" deep link (mirroring overview) so a project-only extension no longer opens as an empty panel in Global scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
…etail User-feedback rework of the extension detail panel: - Install to Agent now shows its target scope via ScopeTargetField — inline folder hint in Global/project mode, required picker in All-scopes mode (buttons disabled until picked), matching the Marketplace install panel exactly - agents already holding a copy in the target scope render as installed (mascot + shield check, faded) instead of disappearing; fresh installs flash; nothing is marked installed before an All-mode target is picked so picking only ever adds check badges - PATHS cards and DOCUMENTATION tabs filter to the active scope (All shows everything); the docs tab re-anchors when scope changes - one shared instancesInScope projection now backs every scope-aware surface (badges, agent filter, scope filter, paths, docs tabs) — removes three hand-rolled copies of the same filter - install-target state resets on scope switch; any in-flight install disables the whole button row (single `deploying` slot) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
A hook's logical name is its command string — the same command in Global and a project is the same logical hook (cross-scope install creates exactly this pair, with the event name translated per agent). Extend the MCP grouping exception to hooks so those copies collapse into one card: the installed check shows correctly after a project-scope install and All mode no longer lists duplicate rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
The delete dialog listed every instance across all scopes even in a project scope, inviting accidental deletion of global copies. Route it through the same instancesInScope projection as the other detail sections: project scope lists project copies, Global lists global ones, All mode remains the cross-scope cleanup surface. Verified on-device: per-entry hook removal from a Kiro project file (sibling entries and other scopes untouched), MCP entry removal from a merged project config, and per-path skill deletion leaving every other agent's copy intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NjDxpjt7gKEdGiyv5SvmnZ
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
The "Install to Agent" action always deployed to the target agent's global config, and was fully disabled in project scope ("coming in a future release"). This PR makes cross-agent install scope-aware end to end: in a project scope, skills / MCP servers / hooks / CLI bundles land in that project's config files — and the whole detail panel (install targets, agent badges, paths, documentation tabs, delete dialog) follows one shared scope projection so no surface ever claims or offers a copy that lives in another scope.
Every project write-path was verified against official docs before implementation (citations in the adapter source comments).
Backend
service::install_to_agentgains a requiredtarget_scope; all four kind branches resolve their write path through the existing*_for(scope)adapter methods. Kiro's global-hook bail-out (Support for Global Hooks (~/.kiro/hooks/) kirodotdev/Kiro#5440) now applies only at Global scope — project-scope hook install to Kiro is exactly the supported alternative, verified firing on-device after workspace trust.sync_extensionswould otherwise prune the new row on the next rescan).ensure_codex_hooks_enabled: per the Codex docs, hooks are enabled by default and[features].hooksis a disable switch — writingtruewas redundant and could trample an explicit user opt-out.AgentInfonow carriesAgentCapabilitiesderived purely from adapter declarations (project_installper kind,hooks_supported,global_hook_install);test_agent_capabilities_matrixpins the full 10-agent matrix as the single source of truth.targetScope(matching every sibling install endpoint); the web handler returns a scope-correct stable id.Frontend
ScopeTargetFieldshows the target scope (inline hint in Global/project mode, required picker in All-scopes mode), all detected agents render with mascots, agents already holding a copy in the target scope show a shield check instead of disappearing, and fresh installs flash.PROJECT_INSTALL_SUPPORTtable and the parallelAGENTS_WITHOUT_HOOKS/ Kiro special-cases are deleted (they had already drifted from the Rust adapters).instancesInScopeprojection backs agent badges, the agent filter, PATHS cards, DOCUMENTATION tabs, and the delete dialog; All mode remains the cross-scope surface.Test plan
cargo test --workspace,cargo clippy,pnpm exec tsc --noEmit,vitest run(260),biome check— all green; every commit reviewed and independently buildable..agents/skills/; MCP → project.cursor/mcp.jsonmerged without touching sibling entries, global config untouched; hook → Kiro project.kiro/hooks/(loads and fires after workspace trust) and Codex project.codex/hooks.jsonwith the globalconfig.tomlbyte-untouched; All-mode install requires an explicit scope pick; per-scope delete removes exactly the checked copies (single hook entry, single MCP entry, single skill dir) leaving every other scope/agent intact.Known limitations (deferred)
.github/hooks/NAME.json, default-branch-only per GitHub docs) and Gemini project hooks are real upstream surfaces but new scan/deploy formats — separate PR.🤖 Generated with Claude Code