Skip to content

Conversation

@khaliqgant
Copy link
Contributor

@khaliqgant khaliqgant commented Feb 11, 2026

Summary

Dashboard UI components for Unified Agent Auth system.

Phase 2:

  • IntegrationConnect.tsx - Provider grid with 15 integrations, Nango OAuth flow
  • IntegrationPolicyEditor.tsx - Policy editor with scope selection (read/write)

Phase 3:

  • Audit log viewer page at /workspaces/[id]/audit
  • Approval request UI with notification badge

Test Plan

  • Provider grid displays all 15 integrations with correct categories
  • Connect/disconnect flow works via Nango
  • Policy editor allows scope selection per provider
  • Audit log viewer shows paginated history
  • Approval notifications appear for pending requests

Made with Cursor


Open with Devin

Agent Relay and others added 2 commits February 11, 2026 19:51
Phase 2 of Unified Agent Auth feature:

- Add IntegrationConnect.tsx: Provider grid with 15 integrations
  organized by category (project, communication, monitoring, deploy,
  storage). Supports Nango OAuth connect/disconnect, tier badges,
  category filters, and compact mode.

- Add IntegrationPolicyEditor.tsx: Policy editor for allowedIntegrations
  field with per-provider scope selection (read/write), wildcard access
  toggle, rate limit configuration, and quick presets.

- Integrate IntegrationConnect into WorkspaceSettingsPanel with new
  "Integrations" tab between AI Providers and Repositories.

- Export new components from components/index.ts

Ref: unified-agent-auth-spec.md
Co-authored-by: Cursor <[email protected]>
Phase 3 of Unified Agent Auth feature:

- Add AuditLogViewer.tsx: Table view of integration proxy requests with
  filters (provider, agent, time range), pagination, and relative timestamps

- Add ApprovalRequestPanel.tsx: Human-in-the-loop approval UI showing
  pending access requests from agents with approve/deny actions

- Add cloudApi methods: getAuditLogs, getApprovalRequests, approveRequest,
  denyRequest

- Add "Audit Log" section to WorkspaceSettingsPanel navigation

- Integrate ApprovalRequestPanel into Integrations settings tab

Ref: unified-agent-auth-spec.md
Co-authored-by: Cursor <[email protected]>
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +200 to +206
setTimeout(() => {
clearInterval(pollInterval);
if (connectingProvider === provider.id) {
setConnectingProvider(null);
setError('Connection timed out');
}
}, 300000);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Stale closure: timeout never clears connecting state on connection timeout

The 5-minute timeout callback at line 202 compares connectingProvider === provider.id, but connectingProvider is captured from the closure at the time handleConnect was created. Since setConnectingProvider(provider.id) was called on line 131 (a state update that hasn't been reflected in the closure), the captured connectingProvider is the previous value (likely null). This means the condition connectingProvider === provider.id will always be false, so the timeout will never reset the connecting state or show the timeout error.

Root Cause: stale closure over React state in setTimeout

When handleConnect is invoked:

  1. Line 131: setConnectingProvider(provider.id) — schedules a state update
  2. Line 200-206: A setTimeout is created that captures connectingProvider from the current closure
  3. The closure's connectingProvider is the value before the setConnectingProvider call (the old state), not provider.id

So when the timeout fires after 5 minutes:

if (connectingProvider === provider.id) { // always false
  setConnectingProvider(null);
  setError('Connection timed out');
}

The condition is always false. The interval is cleared (line 201), but the UI remains stuck in the "Connecting..." state forever, and no timeout error is shown to the user.

Impact: If the OAuth flow takes longer than 5 minutes or silently fails without returning an error status, the user sees an infinite "Connecting..." spinner with no way to recover except refreshing the page.

Suggested change
setTimeout(() => {
clearInterval(pollInterval);
if (connectingProvider === provider.id) {
setConnectingProvider(null);
setError('Connection timed out');
}
}, 300000);
setTimeout(() => {
clearInterval(pollInterval);
setConnectingProvider(prev => {
if (prev === provider.id) {
setError('Connection timed out');
return null;
}
return prev;
});
}, 300000);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +142 to +147
const handleClearFilters = () => {
setFilters({ provider: '', agent: '', startTime: '', endTime: '' });
setCursor(null);
fetchEntries(true);
setShowFilters(false);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 handleClearFilters fetches with old (stale) filter values instead of cleared filters

handleClearFilters calls setFilters(...) to clear filters and then immediately calls fetchEntries(true), but fetchEntries is a useCallback that closes over the current filters state. Since React state updates are asynchronous, fetchEntries still uses the old filter values.

Root Cause: stale closure over filters state

In handleClearFilters (line 142-147):

const handleClearFilters = () => {
  setFilters({ provider: '', agent: '', startTime: '', endTime: '' });
  setCursor(null);
  fetchEntries(true);  // uses OLD filters from closure
  setShowFilters(false);
};

fetchEntries is defined with useCallback(..., [workspaceId, filters, cursor]) at line 104-127. When handleClearFilters calls setFilters(...), the state update is batched and hasn't taken effect yet. The fetchEntries function still references the old filters object from its closure.

So when the user clicks "Clear" after setting filters (e.g., provider='github'), the API request is still made with provider=github instead of no filters. The user sees the filter panel close and expects unfiltered results, but gets the same filtered results.

Impact: The "Clear Filters" button doesn't actually clear filters on the current fetch — it fetches with the old filter values. The filters only take effect on the next fetch (e.g., if the user triggers another action).

Prompt for agents
In AuditLogViewer.tsx, the handleClearFilters function at line 142-147 calls setFilters() and then immediately calls fetchEntries(true), but fetchEntries closes over the stale filters state. To fix this, refactor fetchEntries to accept an optional filters parameter that overrides the state-based filters. For example, change the fetchEntries signature to accept (reset: boolean, overrideFilters?: AuditFilters), and when overrideFilters is provided, use those instead of the closed-over filters state. Then in handleClearFilters, pass the empty filters object directly: fetchEntries(true, { provider: '', agent: '', startTime: '', endTime: '' }). Apply the same pattern in handleApplyFilters if needed, passing the current filters explicitly.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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