Skip to content

feat(extensions): cross-agent install to project scope - #102

Merged
RealZST merged 9 commits into
mainfrom
feat/cross-agent-project-install
Jul 13, 2026
Merged

feat(extensions): cross-agent install to project scope#102
RealZST merged 9 commits into
mainfrom
feat/cross-agent-project-install

Conversation

@RealZST

@RealZST RealZST commented Jul 13, 2026

Copy link
Copy Markdown
Owner

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_agent gains a required target_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.
  • Installs into unregistered projects are rejected up front with a clear message (sync_extensions would otherwise prune the new row on the next rescan).
  • Removed ensure_codex_hooks_enabled: per the Codex docs, hooks are enabled by default and [features].hooks is a disable switch — writing true was redundant and could trample an explicit user opt-out.
  • AgentInfo now carries AgentCapabilities derived purely from adapter declarations (project_install per kind, hooks_supported, global_hook_install); test_agent_capabilities_matrix pins the full 10-agent matrix as the single source of truth.
  • Adapter corrections per official docs: Windsurf has no workspace MCP config (declaration removed + pinning test); Cursor's unsourced "2.4+" claim dropped; Antigravity skills source upgraded to the official codelab.
  • Web + desktop endpoints take a required targetScope (matching every sibling install endpoint); the web handler returns a scope-correct stable id.

Frontend

  • Install to Agent mirrors the Marketplace panel: ScopeTargetField shows 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.
  • Gating reads the backend capabilities everywhere; the hand-maintained PROJECT_INSTALL_SUPPORT table and the parallel AGENTS_WITHOUT_HOOKS / Kiro special-cases are deleted (they had already drifted from the Rust adapters).
  • One shared instancesInScope projection backs agent badges, the agent filter, PATHS cards, DOCUMENTATION tabs, and the delete dialog; All mode remains the cross-scope surface.
  • Hooks now group across scopes by command (like MCP by name), so the project copy created by a cross-agent install merges into the same card even when the event name is translated per agent.
  • 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.

Test plan

  • cargo test --workspace, cargo clippy, pnpm exec tsc --noEmit, vitest run (260), biome check — all green; every commit reviewed and independently buildable.
  • On-device E2E (macOS): skill → project .agents/skills/; MCP → project .cursor/mcp.json merged without touching sibling entries, global config untouched; hook → Kiro project .kiro/hooks/ (loads and fires after workspace trust) and Codex project .codex/hooks.json with the global config.toml byte-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.
  • Live API check: installing into an unregistered project returns 400 with a clear message.

Known limitations (deferred)

  • Copilot project hooks (.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.
  • Toggle still flips every scope's copy of a group; per-scope toggle needs a detail-panel interaction redesign.
  • The web endpoint's returned id for hooks can mismatch the scanned row when the event name is translated (pre-existing; the frontend discards the value).

🤖 Generated with Claude Code

RealZST and others added 9 commits July 12, 2026 11:52
- 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
@RealZST
RealZST merged commit e4e569d into main Jul 13, 2026
3 checks passed
@RealZST
RealZST deleted the feat/cross-agent-project-install branch July 13, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant