* Pick a private-capable proxy cluster when bootstrapping the agent network
The connect-provider wizard bootstraps the account's agent network
endpoint from a proxy cluster it auto-picks off /reverse-proxies/domains,
taking the first validated free domain. That list includes clusters that
cannot host the endpoint: the endpoint is a private service, reachable
only from connected peers and authorised by their tunnel identity, which
needs a cluster with at least one connected embedded proxy — the same
supports_private flag the Reverse Proxy modal gates NetBird-Only Access
on. Management refuses a bootstrap onto a cluster reporting it false, so
the wizard could propose a pick the API rejects, and the endpoint it
assigns is immutable.
Filter the candidates on supports_private before auto-picking, and give
the "nothing to pick" callout a second message for the case where
clusters exist but none of them has an embedded proxy — a different
problem from having no proxy at all, and a different fix.
Only an explicit false disqualifies a cluster: a management build that
predates the flag reports nothing at all, and dropping every cluster
there would block setup on a backend that would have accepted it.
Callout takes an optional data-testid so a spec can assert on the state
it announces rather than on its copy.
* Derive the bootstrap cluster instead of holding it in state
Nothing picks the bootstrap cluster: there is no selector, so the state
could only ever mirror the first usable entry of the domains list, with
an effect writing it back on arrival and a render pass to show it. Derive
it from that list instead, which drops the state, the effect and the two
resets that kept them in sync.
* Declare the e2e proxy private so a bootstrap cluster exists
The connect-provider wizard now offers only clusters whose proxies report
the private capability, because the agent network endpoint is a private
service and management refuses to bootstrap it anywhere else. The e2e
environment's reverse-proxy ran with NB_PROXY_PRIVATE at its default of
false, so with that filter in place no cluster qualified: the wizard
showed the no-cluster callout, Continue stayed disabled, and every spec
that walks through the wizard failed on the Models tab it could no longer
reach — agentgateway, Kimi and the refused-save spec.
Declare the capability on the primary proxy. The no-ports proxy stays
centralised, so the pick also has to skip past a cluster that does not
qualify, which is the case the wizard filter exists for.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6
* Send the no-cluster callout to the agent network Clusters section
The callout told users to connect a proxy under Reverse Proxy, the generic
services page. Agent Network has its own Clusters tab under Configuration,
which is where a user setting up the agent network expects to manage the
clusters it runs on, so both messages link there instead. The tab and the
callout gate on the same permission (services read), so every role that
sees the callout can follow the link.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6
* Say private capabilities, not embedded proxy, in the no-cluster callout
The requirement is a cluster with private capabilities, which is what the
supports_private flag reports. Tying the message to an embedded proxy named
one way of getting there as if it were the requirement itself, and pointed
users at the wrong fix. The callout now states the requirement and where to
connect such a cluster.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Disabled routing peers, network resources, policies and legacy routes were
only distinguishable by a faded row, and the fade also dimmed the controls
needed to re-enable them. Bring back the Active toggle column that was
hidden in #654, and add a cellClassName hook to DataTable with a shared
fadeDisabledRowCells helper that fades every cell of a disabled row except
the toggle and actions cells.
Bump next from 16.3.0 to 16.3.4 and refresh the transitive lockfile
entries that came with it (@next/* swc binaries, sharp 0.35.4,
rolldown, vitest, playwright).
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove the unreachable FirewallGPT module and the abandoned traffic-event
detail prototypes, along with the dependencies they were the sole consumers of.
FirewallGPT was disabled in 9d8d9ee4 by removing its trigger button, leaving
the modal mounted but impossible to open. TrafficEventDetails was never
imported anywhere, and it was the only consumer of TrafficEventChart.
Dependency changes:
- Drop @faker-js/faker, whose only usage was mock chart data in the removed
TrafficEventChart. This also resolves GHSA-qxc2-j82w-r537 without needing
the 9 -> 10 major upgrade.
- Drop react-confetti-explosion, only used by the removed
FirewallGPTSuccessModal.
- Bump @types/node to 20.19.43 so it satisfies the peer range of the vite
version pulled in by vitest, silencing an ERESOLVE warning on install.
Stays on the 20.x line to match the Node 20 used in CI.
- Add an allowScripts deny policy for the four packages with install scripts.
Both @axa-fr postinstalls are bare echo statements, fsevents ships a
prebuilt binary, and the unrs-resolver postinstall only verifies a binding
that optionalDependencies already installs.
Surfaces the two bundle parameters added to the management API: an
anonymization level (Default/Strict, shown when anonymization is enabled)
and an optional upload service URL that overrides the default upload
server. The upload URL is validated as an https URL before the job can be
submitted; both fields are omitted from the request when left at their
defaults so the peer applies its own.
Stabilize the flaky Control Center and agentgateway e2e specs (#788)
Three separate causes behind the recent e2e failures.
The group view ranked every group in the account by peer count to decide what
to display, so with two workers it could land on a group another spec had just
created. openGroupView() selects the group by name instead.
Management now checks a provider's upstream URL when the provider is saved and
rejects it unless the URL returns a model list, so the agentgateway spec's
placeholder host stopped working. The test environment runs a stub that answers
the check.
The Backspace test blurred the group panel's search input before that input had
taken focus, so it reclaimed focus and swallowed the key.
The peer view matched policies only by source group membership, so a
policy whose source is the peer itself never appeared in that peer's
view. There was no group fallback either: a rule carrying a
sourceResource has its sources nulled on save, so the group check could
only ever return false for exactly these policies.
Such policies are reachable from the canvas itself, since dragging a
connection off a peer node writes a sourceResource rather than a group.
A user could create a peer-sourced policy, deploy it, drill into that
same peer and not see it.
Match rule.sourceResource against the peer id alongside the group check.
The rendering side needs no change, as select-peer-node is the peer and
its edge to the policy is drawn unconditionally. Draft mode is fixed
transitively: useDraft harvests visible policies from the live view's
nodes, and already draws a sourceResource peer as a source card.
A refused save closed the modal anyway, throwing away the key just typed — and
the API never returns one. Both paths now stop before closing. The failure was
also reported twice and, because notify() only reddens through its promise path,
both times as a success.
Load and submit now spin, say what they are waiting on, and stop taking clicks;
dismissal is refused mid-save, since the write lands regardless and would
otherwise close a session reopened in the meantime.
Discovery carries the record id and the form's URL together, so changing an
endpoint no longer demands the key back. A 422 from it was reported as
unreachable rather than unsupported — useApiCall rejects with { code, message }
and the hook read status, leaving the catalog fallback unreachable.
Discovered models arrived at zero: 0/0 was hardcoded for anything the catalog
lacked by exact id, which stopped being true once discovery returned real rates.
Bedrock felt all of it — its geography-prefixed ids never match by string.
A Playwright spec covers the refusal end to end.
* Add Agent Network Admin and Usage Viewer roles, gate Agent Network per submodule
The management server now ships two delegated roles (agent_network_admin,
usage_viewer) and reports agent_network submodule permissions
(agent_network.providers, .policies, .usage, .logs, .settings, ...) in
/users/current. The dashboard still gated the whole Agent Network section
on services.read, so the new roles saw either everything or nothing.
- register the agent_network module keys so PermissionsProvider fills
denied defaults and typed lookups work
- gate the Agent Network sidebar and pages per submodule: providers,
policies, usage (usage or logs read), configuration (settings)
- show the section when any child is permitted instead of services.read
- add both roles to the role selector, user/invite tables and badges
* Gate Usage and Clusters tabs by their own permissions
A caller holding only agent_network.logs.read landed on the Usage tab it
cannot read, and one holding only agent_network.usage.read could open
Access Logs. Show each tab only with its matching read permission and
fall back to the first permitted tab on deep links.
The Clusters tab is a reverse-proxy surface: its table and controls run
on the services permission, so agent_network.settings alone (e.g.
agent_network_admin) no longer surfaces it.
* Fall back to a known configuration tab for unknown query values
An unknown ?tab= value selected no content pane and left the
configuration page blank; unknown or unauthorized values now fall back
to Global Limits.
* Derive the configuration tab from the URL query
The ?tab= query was mirrored into local state through an effect, which
can lag a render behind the URL and duplicates the source of truth.
Derive the active tab from the query directly; trigger clicks already
push the query themselves, and onChange keeps pushing it for Radix's
keyboard navigation, which fires without a click.
* Derive the usage tab from the URL query
Same treatment as the configuration page: the ?tab= query was mirrored
into local state through an effect, duplicating the source of truth.
Derive the active tab from the query; onTabChange keeps pushing it so
clicks, deep links and back/forward all resolve the same way.
* Group user roles into General and Agent Network tabs
Tab the role dropdown by product surface, gate the Agent Network
roles and tab on the Agent Network surface being available, and
disable the role field for the owner unless the caller is an owner.
* Point agent network learn-more links at their docs pages
* Skip the domains lookup when the role can't read it
* Flatten the role selector and add a caller-scoped My Setup page
The role dropdown split General and Agent Network roles into tabs,
which reads as two products inside one field. Return to the single
flat list from main, keeping the surface gate (Agent Network roles
hidden when the surface is unavailable) and the owner protections.
Regular users had no way to see which providers and models their
policies allow or where to point their tools. Add
/agent-network/my-setup backed by the caller-scoped me/setup and
me/consumption endpoints: the endpoint with copy, per-provider
available models, the Configure Your Agent instructions flattened
inline from the connect modal, and the caller's own usage counters.
The sidebar surfaces it for any role — limited view included —
whenever the caller's setup is configured, and stays hidden when
not, which is indistinguishable from no access by design.
* Fix delegated-role API errors and reuse the usage overview for My Usage
Field testing surfaced three problems. The policy and budget modals'
group selector fetched /networks/resources unconditionally, so an
agent_network_admin without the networks permission got a 403 and an
error toast on the policies page; gate the fetch on networks read.
Restricted users got no navigation at all, so the limited view could
never reach My Setup; render the sidebar for restricted users when
their setup is configured or their role reads an agent_network surface.
Roles below account admin also cannot read the account feature flag
(accounts read), so holding an explicit agent_network grant now counts
as proof the surface exists, while callers with accounts read keep the
flag as the source of truth.
My Usage rendered raw per-window counter rows, nothing like the admin
usage view; render it with the same chart-and-table component as the
admin overview, fed from the caller-scoped me/usage/overview endpoint
that returns the identical bucket shape filtered to the current user.
* Reuse the providers-page agent config on My Setup and self-scope Usage & Logs
My Setup now presents the endpoint exactly like the providers page — the
API Base URL badge with Copy and Agent Config opening the same modal
(provider ids come from the caller's me/setup answer instead of the
admin-permissioned provider context) — and drops the My Usage tab: the
regular Usage & Logs page now serves callers without the account-wide
grants too, showing their own data (the server pins the filters to them),
with the identity and provider filters trimmed since they'd be overridden.
usage_viewer gets working filters: the feature-flag fallback moves into
useAgentNetworkMode, AIProvidersProvider gates each fetch on its
submodule read grant so partially-granted roles don't fire 403s, and
GroupsProvider serves restricted roles that hold the groups read grant.
The limited view also gains the Peers link (the add-your-device page).
* Hide provider write actions from read-only viewers
usage_viewer now reads the provider list, but the page still offered every
write flow: Connect Provider, row-click edit, and the enable/disable/delete
menu. Clicking any of them opened the wizard, which - unable to read the
settings row - warned that Agent Network isn't set up even on configured
accounts. Each action is now gated on its own operation grant: the connect
button needs providers create, row-click edit needs update, and the menu
items need update/delete respectively (no menu at all without either).
* Restore the grouped role selector tabs
Bring back the General and Agent Network tabs in the role dropdown
from 3fbeb1e, undoing the flattening: the tabbed grouping keeps the
list readable as more surfaces ship their own delegated roles. The
surface gate (Agent Network roles hidden when the surface is
unavailable) and the owner protections are unchanged.
* Offer the provider and model filters to self-scoped callers
The providers endpoint now self-scopes on the server, so a configured
plain user fetches it too and the caller's own authorized providers back
the Provider and Model filter options on the self-scoped Usage & Logs
view. The identity filters stay hidden — the server pins user and group
to the caller regardless.
* Give the agent config a single home on Connect Agent
The endpoint card carried an Agent Config action on both the providers
page and the caller-scoped self-service page, so the same connect
snippets had two entry points. EndpointBadge now renders it only when
the caller passes providerIds, which is just the self-service page; the
providers page keeps the URL and Copy. The context-backed
AgentConnectModal wrapper is then unused, so it goes.
Rename that page from My Setup to Connect Agent in the sidebar,
breadcrumb and heading: My Setup was the only first-person label in the
navigation, and Connect Agent stays clear of the Configuration sibling.
* Simplify Bedrock config
* Inline the agent config on the Connect page
Drop the Agent Config modal: its snippets now render inline under the
endpoint card, so AgentConnectModal is just AgentConnectTabs and
EndpointBadge is back to the URL and Copy. Move the route to
/agent-network/connect and swap the hand-rolled provider list for the
standard DataTable. Follow the backend rename of me/setup to
agent-config.
* Show a policy's allowed models in the policies table
A policy's model restrictions live on the guardrails attached to it, so
the new Models column unions the enabled model allowlists: one chip for
a single model, a count with the names on hover past that, and a
Restrict badge opening the guardrails tab when nothing narrows the
policy. Clicking the column lands on that tab too.
Chip the models on the Connect Agent table instead of comma-joining
them, and capitalize the model counts across all three tables to match
the "2 Groups" chips on Peers.
* Point the Kimi e2e config assertions at the Connect Agent page
The Kimi-gated config surfaces moved: the Agent Config modal is gone and
the snippets render inline on /agent-network/connect, so the spec stopped
finding the button it used to click on the providers page. Navigate to the
Connect page instead.
That page's answer is caller-scoped — it only offers providers the
caller's own policies authorize — so the spec now grants the owner the
freshly connected provider through a dedicated group + policy, torn down
again (group detached from the owner first) before and after the run. The
backing GET /agent-network/agent-config ships with newer management
builds; on builds that predate it the suite skips, same as the existing
catalog and settings-bootstrap probes.
* Hand the agent config to every account member
The agent-config answer withheld the endpoint from callers no policy
covers, so "account not set up" and "you have no access" were the same
shape. The dashboard shows every user the same connection config, so
Configured now tracks the account: once it has an endpoint every member
gets it, with an empty providers list for those no policy covers. That
list stays caller-scoped, and the endpoint authorizes nothing on its own
since the gateway still refuses every request no policy permits.
Rename the files and symbols the endpoint outgrew when it became
/agent-network/agent-config: me_handler.go, setup.go and types/setup.go
become agent_config.go, GetSetupForUser becomes GetAgentConfigForUser,
and the AgentNetworkMeSetup schema becomes AgentNetworkAgentConfig.
Regenerating types.gen.go from the untouched spec first reproduced it
byte for byte, so the generated diff is only these renames.
* Let the surface switch decide the Agent Network section
Connect Agent and Usage & Logs were visible on the caller's own setup
alone, so a deployment with Agent Network off still showed them, and the
route tree then 404d the links the nav had just rendered. The switch now
decides first.
The caller's agent config still stands in for the switch, but only for
callers who cannot read the config themselves - otherwise an account
holding an endpoint kept the surface on for its owner regardless.
* Collapse the two User types the merge left behind
Both sides of the merge declared a User type, in different regions of the
helper, so the merge took both without ever reporting a conflict and
Playwright refused to parse the file. One type now carries the union of
the fields the /users answer returns.
* Fix SNAPSHOT version display
* Fix e2e tests
* Fix e2e tests
* Fix version display
* Fix e2e
* Fix e2e
* Fix e2e
---------
Co-authored-by: braginini <bangvalo@gmail.com>