Skip to content

feat(agentex-ui): account picker via same-origin BFF proxy#350

Open
erichwoo-scale wants to merge 9 commits into
mainfrom
feat/agentex-ui-account-picker
Open

feat(agentex-ui): account picker via same-origin BFF proxy#350
erichwoo-scale wants to merge 9 commits into
mainfrom
feat/agentex-ui-account-picker

Conversation

@erichwoo-scale

@erichwoo-scale erichwoo-scale commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

First of a 2-PR stack bringing account scoping + OIDC login to agentex-ui. This base PR routes the SDK through a same-origin BFF and adds an account switcher. Chart + deploy side lives in scaleapi/sgp#3961.

  • Routes the agentex SDK through a same-origin BFF (/api/agentex) instead of calling the API directly from the browser — the upstream URL and credentials never reach client JS.
  • Shared applyBffCredentials forwards x-selected-account-id, drops any client-sent Authorization, and strips the account-scoped _jwt cookie so SGP honors the selected account rather than the one you linked in with (identity stays via _identityJwt). The proxy also strips Location on upstream 3xx so internal redirect targets don't leak.
  • Account selection is driven by the account_id query param (no cookie), injected on every SDK request via a synchronous ref. Switching accounts resets the open task + selected agent to the account's home grid, and resetQueries drops the previous account's cached data so it never briefly renders the old agents.
  • /api/user-info fetches the caller's accounts; the picker bootstraps to the first when the param is missing/stale, shows a disabled/empty loading state (not a skeleton), renders single-account as static context, and lives in the sidebar footer (collapsed → icon-only).
  • Env-gated on the platform API being configured (SGP_API_URL / NEXT_PUBLIC_SGP_APP_URL); no-op otherwise.

Env: NEXT_PUBLIC_AGENTEX_API_BASE_URL → server-only AGENTEX_API_URL.

In this PR the BFF runs in default (cookie) mode — no auth dependency. The token-hiding Bearer path arrives in the stacked PR.

Demo

account_picker_demo.mov

Stack

  1. This PR — account picker + same-origin BFF proxy → main
  2. feat(agentex-ui): OIDC login with server-side access token #351 — OIDC login with server-side access token → stacked on this branch
  3. scaleapi/sgp#3961 — Helm chart + system-manager pack (deploy side)

Test plan

  • npm run typecheck / npm run lint — clean
  • Runtime: switching accounts re-scopes agents/tasks (and SGP calls via the _jwt strip); single / multi / no-account render; loading state; no stale-account flash

🤖 Generated with Claude Code

Comment thread agentex-ui/app/api/agentex/[...path]/route.ts Outdated
Comment thread agentex-ui/app/api/agentex/[...path]/route.ts
@erichwoo-scale

Copy link
Copy Markdown
Contributor Author

Addressed the Greptile findings in bb2eb06:

  • P1 — authorization not managed: applyBffCredentials now deletes any client-supplied Authorization header before forwarding, so a client can't inject its own bearer token to the upstream. (The proxy comment is now accurate — credentials really are managed there.)
  • P2 — upstream Location leak: the proxy strips Location from upstream 3xx responses so internal redirect targets don't reach the browser.

Also, per UX design feedback, moved the account picker out of the header/collapsed-top rail into the sidebar footer (below Give Feedback) — top of the sidebar is untouched.

erichwoo-scale and others added 5 commits July 8, 2026 15:59
Route the SDK through a same-origin BFF (`/api/agentex`) instead of calling
the agentex API directly from the browser, and add an account switcher.

- BFF proxy + shared `applyBffCredentials` forward the request cookies and an
  `x-selected-account-id` header to the upstream (agentex + platform APIs).
- The selected account is driven by the `account_id` query param — no cookie —
  and injected on every SDK request via a synchronous ref.
- `/api/user-info` fetches the caller's accounts; the picker bootstraps to the
  first account when the param is missing/stale (the API needs one to resolve a
  principal). Single account renders as static context; collapsed rail shows an
  icon-only variant.
- Gated on the platform API being configured (`SGP_API_URL` /
  `NEXT_PUBLIC_SGP_APP_URL`); no-op otherwise.

Env: `NEXT_PUBLIC_AGENTEX_API_BASE_URL` → server-only `AGENTEX_API_URL`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oter

Address Greptile review + UX design feedback.

- BFF: drop any client-supplied `Authorization` in applyBffCredentials so a client
  can't inject its own bearer token to the upstream (P1); strip `Location` from
  upstream 3xx responses so internal redirect targets don't leak to the browser (P2).
- UX: move the account picker out of the sidebar header / collapsed top rail into
  the footer, directly below Give Feedback (top of the sidebar now untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the account picker's loading skeleton with a disabled, empty picker (building
icon only) — a plain skeleton block read as odd once the picker moved into the footer
between Give Feedback and Log out. Keeps the row stable and reads as "loading account
selector".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…switch

- Account picker: extract a shared `iconTile` (loading + single-account collapsed
  tiles) and a shared `options` element (the SelectContent list previously duplicated
  across the collapsed and expanded switchers); single change handler `onAccountChange`.
- Provider: `setSelectedAccountId` now clears `task_id` on an explicit switch (the open
  task is account-scoped and 404s under a new account). This preserves the reset that
  previously lived in agentex-ui-root's validation effect, which #347 reworked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The STRIP_REQ comment previously read as if `authorization` went unmanaged. Spell out
that applyBffCredentials deletes any client-supplied `cookie`/`authorization` and sets
the server-managed values, so the stripping applies to every /api/* proxy — addressing
the review note without moving credential logic out of the shared helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-account-picker branch from 8eb5b47 to 5f243a0 Compare July 8, 2026 20:00
@declan-scale

Copy link
Copy Markdown
Collaborator

Will we need to update the sgp repo to pass in the AGENTEX_API_URL instead of NEXT_PUBLIC_AGENTEX_API_BASE_URL?

@erichwoo-scale

erichwoo-scale commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Will we need to update the sgp repo to pass in the AGENTEX_API_URL instead of NEXT_PUBLIC_AGENTEX_API_BASE_URL?

yup working on the corresponding sgp PR, and will hold off on merging until thats ready

…e flash

Switching accounts closes the open task (task_id cleared), which flips PrimaryContent
from ChatView to HomeView immediately. HomeView rendered the agents list from the
still-cached previous account (invalidateQueries keeps data during the refetch), so the
new account briefly flashed the old account's agents before recalibrating.

Use resetQueries instead: the previous account's agents/tasks are dropped on switch, so
HomeView shows a loading state until the new account's data lands — never stale data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread agentex-ui/app/api/user-info/route.ts
erichwoo-scale and others added 2 commits July 8, 2026 17:06
Trim redundant/verbose comments across the account-picker BFF layer (drop lines that
restate the code or repeat a docstring); no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…itch

A selected agent is account-scoped, but its name persisted in the URL across a switch —
so a same-named agent in the new account stayed selected (and #347's localStorage restore
could reinstate it). On an account change, clear agent_name and the remembered agent so
the view resets to the grid. task_id is already cleared by the switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-account-picker branch from 7530498 to 54e5b75 Compare July 8, 2026 21:31
Cookie-auth backends (SGP's identity-service-auth) resolve the account from the `_jwt`
access-profile cookie when it's present and only fall back to `x-selected-account-id`
when it's absent — so forwarding `_jwt` pins requests to the account the user linked in
with and silently ignores the account they switched to. Drop `_jwt` before forwarding
(default/cookie mode) so the selected account wins; identity stays via `_identityJwt`.
agentex is unaffected (it never reads `_jwt`), and auth mode drops all cookies anyway.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants