From 5372d65b26ab391f5d3896e96e0ba3803fe9c33e Mon Sep 17 00:00:00 2001 From: Minas Arustamyan Date: Tue, 5 May 2026 05:17:05 +0200 Subject: [PATCH] fix(setup): install list reflects opt-outs + Store bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `compute_default_agent_prompt` (which renders the install commands in the setup prompt's marketplace block) was calling `resolve_allowed_plugins` — the admin-only feed that predates the v25 Store/opt-out layer. Result: a user with 2 opted-out curated plugins + 2 Store skills saw the original 4 admin grants in the install list (including the opted-out ones, with cross-marketplace duplicates), and no `agnes-store-bundle` install line for the skills. Now we call `resolve_user_marketplace` — the same resolver that `/marketplace.zip` + `/marketplace.git/` serve from. The install commands now match the served catalog exactly: admin grants minus the user's opt-outs, plus the `agnes-store-bundle` synth plugin (which wraps every installed Store skill + agent into one plugin entry) and any standalone Store plugin uploads. Dedup by `manifest_name` because two upstream marketplaces shipping a plugin with the same name collide in the synth marketplace.json by design (CLAUDE.md "Same-named plugins ... collide in the catalog by design"). A duplicate `claude plugin install @agnes` would be a no-op anyway, so it's just visual noise to keep emitting both. --- src/welcome_template.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/welcome_template.py b/src/welcome_template.py index 3715514..c6b157b 100644 --- a/src/welcome_template.py +++ b/src/welcome_template.py @@ -154,19 +154,32 @@ def compute_default_agent_prompt( _wheel = _find_wheel() _wheel_filename = _wheel.name if _wheel else "agnes.whl" - # RBAC plugin resolution is unconditional — same code path for - # admin and non-admin. Users with no `resource_grants` rows get an - # empty list and the no-marketplace layout; users with grants get - # the marketplace block. Admin-vs-analyst is no longer a layout - # branch. + # The install commands emitted in the marketplace block must match + # exactly what /marketplace.zip + /marketplace.git/ serve. That's + # the `resolve_user_marketplace` view: admin grants minus the + # user's opt-outs, plus their Store installs (skills + agents + # rolled up into the synth `agnes-store-bundle` plugin, plugin- + # typed entities standalone). `resolve_allowed_plugins` was the + # pre-store admin-only feed and would emit installs for plugins + # the user has opted out of, while skipping the bundle entirely. + # + # Dedup by manifest_name handles the documented case where two + # upstream marketplaces ship a plugin with the same name (see + # CLAUDE.md "Same-named plugins ... collide in the catalog by + # design"). The synth marketplace.json carries one entry per + # name; a second `claude plugin install @agnes` would be + # a no-op anyway. plugin_install_names: list[str] = [] if user and conn is not None: try: from src import marketplace_filter - plugin_install_names = [ - p["manifest_name"] - for p in marketplace_filter.resolve_allowed_plugins(conn, user) - ] + seen: set[str] = set() + for p in marketplace_filter.resolve_user_marketplace(conn, user): + name = p["manifest_name"] + if name in seen: + continue + seen.add(name) + plugin_install_names.append(name) except Exception: logger.exception("compute_default_agent_prompt: marketplace plugin resolution failed")