SIGN IN SIGN UP

AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.

0 0 73 Python

fix(platform/copilot): Unify & debug credential UX (card + prompt + session store + popup recovery) (#13088)

## Why

The Copilot credential UX was fragmented and brittle. An eight-point
audit of the surface area surfaced these concrete problems:

- Three near-duplicate `SetupRequirementsCard` components in
`run_block`, `run_agent`, and the (now-unified) shared location, each
diverging slightly in copy and logic.
- The Reasoning indicator was a generic shadcn accordion with a chevron
and no visual signal that the model was actively thinking, and required
scrolling back up to the trigger to dismiss.
- A hallucinated "a sign-in card has appeared" message was being emitted
by the model with no card actually being produced — the assistant told
the user to click a card that did not exist.
- `connect_integration` built its `missing_credentials` payload via a
bespoke `_CredentialEntry` TypedDict, diverging from the shared
`run_block` / `run_agent` serializer (different sort order, no
discriminator support).
- After clicking through a credential card, subsequent cards in the same
chat for the same provider still showed a stale prompt — the original
workaround was a component-local `isDismissed` flag in
`ConnectIntegrationTool` that did not survive a `SetupRequirementsCard`
remount mid-stream.
- When the browser blocked an OAuth popup, the helper silently fell back
to opening the URL in a new tab but the waiting modal kept saying
"Complete the sign-in process in the pop-up window…" indefinitely.

This PR addresses seven of the eight; one is deferred with rationale
below.

## What

| # | Change | Surface | Net LoC |
|---|---|---|---|
| 1 | Unify the credential setup card | Frontend | -78 |
| 2 | Reasoning indicator: pulse + inline Collapse | Frontend | +85 |
| 3 | Backend prompt: eager card surfacing + anti-hallucination
guardrails | Backend | +49 |
| 4 | `connect_integration` routes through the shared serializer |
Backend | -10 |
| 5 | Session-scoped connected-providers store | Frontend | +160 |
| 6 | OAuth popup-blocked recovery | Frontend | +44 |

## How

### 1. Unify the credential setup card

Collapses three near-duplicate cards into one shared component at
`copilot/components/SetupRequirementsCard/`. Adds an `inputsMode: "edit"
| "preview"` prop so `run_agent` gets the read-only inputs preview and
`run_block` / `connect_integration` get the editable RJSF form. MCP
keeps its own card (different OAuth route). No behaviour change.

### 2. Reasoning indicator: pulse + inline Collapse

Drops the trailing chevron (was emitted by the shared shadcn
`AccordionTrigger`), adds `animate-pulse` to the trigger label, and puts
a small `Collapse` button at the bottom of the expanded content so users
don't have to scroll back to the trigger to dismiss. Pulse is driven by
an `isActive` prop that maps to the AI SDK reasoning part's `state ===
"streaming"` so a finalized reasoning block does not look like the model
is still thinking.

### 3. Backend prompt: eager card surfacing + anti-hallucination

Adds a "Credentials & sign-in surfacing — CRITICAL" section to the
shared tool notes (`prompting.py`). Three rules:

- **Surface eagerly:** call `connect_integration` / `run_agent` /
`run_block` in the SAME turn, before collecting other inputs. Don't wait
until you have the URL / resource ID / etc.
- **Anti-hallucination:** never claim "a sign-in card has appeared"
unless you have just emitted one this turn. Call the tool first, then
describe it.
- **Prefer the tool over verbal coaching:** if you would write "please
connect your GitHub account", instead just call
`connect_integration(provider="github")`.

Backed by three new `prompting_test` assertions that fail loudly if any
of the three rules drifts out of the prompt.

### 4. `connect_integration` through the shared serializer

`connect_integration` used a bespoke `_CredentialEntry` TypedDict. Now
routes through `build_missing_credentials_from_field_info()` like
`run_block` / `run_agent` so provider/scope/type semantics live in one
place. Display-name override preserved so the title stays "GitHub
Credentials" rather than the title-cased slug "Github Credentials".

### 5. Session-scoped connected-providers store

New Zustand store keyed by `${sessionID}::${provider}`. When the user
clicks Proceed on a credential card, every provider it required is
marked connected for that session. The card's render guard checks the
store: when an earlier card has already satisfied the requirement, the
new card self-dismisses to "Connected. Continuing…" instead of showing a
stale prompt.

This obsoletes the local `isDismissed` workaround that
`ConnectIntegrationTool` carried only to survive `SetupRequirementsCard`
remounts mid-stream — the store handles that now.

7 store unit tests + 3 new card-integration tests covering the
session-scoped dismissal behaviour.

### 6. OAuth popup-blocked recovery

`openOAuthPopup` already fell back to a new tab when the browser blocked
the popup, but the user had no way of knowing — the waiting modal kept
saying "Complete the sign-in process in the pop-up window…"
indefinitely. Now it returns a `popupBlocked: boolean` flag.
`useCredentialsInput` tracks the flag, emits a "Popup blocked" toast
(`OAUTH_ERROR_POPUP_BLOCKED` constant for the message), and
`OAuthFlowWaitingModal` swaps its copy to direct the user to the new tab
(or to allow popups and retry).

## Verified / no-op (no code change needed)

- **Backend `_check_prerequisites` already handles the creds-only gap.**
The credential gate runs before the input gate (`run_agent.py:530`
before `:557`), so a user with no creds and no inputs gets the setup
card first — no code change needed.
- **Multi-account connection picker already exists** in
`CredentialsFlatView`, which renders `CredentialsSelect` (a dropdown)
when there are ≥2 stored credentials for the same provider. Every
`SetupRequirementsCard` consumes it via `CredentialsGroupedView →
CredentialsInput → CredentialsFlatView`.

## Deferred

- **Proactive token-health probe in `_check_prerequisites`.** A pre-run
refresh of every OAuth token to surface a Reconnect card before
execution starts. Deferred because (a) the existing race-recovery path
at `run_agent.py:400-448` already rebuilds the card when credential
errors surface mid-run, (b) probing adds per-provider network latency to
every `run_agent` call, (c) testing it well requires mocking each
provider's refresh handler. Belongs in its own PR with focused review.

## Risk / rollout

- Pure refactor on the frontend side (1, 2, 5, 6) — same backend
contract, same component public API plus one optional prop
(`inputsMode`).
- Backend changes (3, 4) are additive: prompt text plus an internal
refactor of `connect_integration` that produces a payload shape
identical to `run_block` / `run_agent` (verified by test).
- No DB migrations, no env-var changes, no feature flags.
- Rollback: revert this commit; the deleted-and-recreated
`SetupRequirementsCard` files at the legacy paths come back together,
and the Zustand store disappears. No state is persisted server-side, so
the rollback is instantaneous.

## Test plan

- [x] `pnpm test:unit` (scoped to touched paths): 192/192 pass.
- [x] `pnpm lint` clean; only pre-existing image-tag warnings.
- [x] `pnpm format` clean.
- [x] `pnpm types`: 1 pre-existing error in
`library/agents/[id]/__tests__/trigger-agents.test.tsx` (missing
`@testing-library/user-event` import); 0 errors in files this PR
touches.
- [x] `poetry run pytest backend/copilot/prompting_test.py
backend/copilot/tools/connect_integration_test.py`: passes (prompting
tests include the three new guardrail assertions).
- [ ] **Manual:** run a `run_block`, `run_agent`, and
`connect_integration` end-to-end in dev to confirm identical UX (same
card, same flow), then re-run a second card for the same provider in the
same chat to confirm session-store auto-dismissal kicks in.
- [ ] **Manual:** trigger an OAuth flow with popups blocked in the
browser (Chrome DevTools → "Block popups" or Brave shields) to confirm
the modal copy switches and the toast fires.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
U
Ubbe committed
7ae7b17f9c0308650b39c2891babf8856e190d42
Parent: 9789cf4
Committed by GitHub <noreply@github.com> on 5/15/2026, 8:04:39 AM