This commit is contained in:
Caprika 2026-05-31 13:23:25 +08:00 committed by GitHub
commit 4277bbc310
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 2 deletions

View file

@ -196,6 +196,7 @@ export function AmrLoginPill({
const pollRef = useRef<number | null>(null);
const loginStartedAtRef = useRef<number | null>(null);
const loginPendingRef = useRef(false);
const suppressLoginInFlightRef = useRef(false);
const stopPolling = useCallback(() => {
if (pollRef.current !== null) {
@ -215,6 +216,7 @@ export function AmrLoginPill({
return () => {
loginPendingRef.current = false;
loginStartedAtRef.current = null;
suppressLoginInFlightRef.current = false;
stopPolling();
};
}, [refresh, skipInitialRefresh, stopPolling]);
@ -245,6 +247,7 @@ export function AmrLoginPill({
stopPolling();
loginStartedAtRef.current = null;
loginPendingRef.current = false;
suppressLoginInFlightRef.current = false;
setPending(null);
return;
}
@ -257,6 +260,7 @@ export function AmrLoginPill({
}
loginStartedAtRef.current = null;
loginPendingRef.current = false;
suppressLoginInFlightRef.current = false;
setPending(null);
setErrorMessage(t('settings.amrLoginErrorCompact'));
}
@ -270,6 +274,7 @@ export function AmrLoginPill({
const onStatusChange = (event: Event) => {
const reason = amrLoginStatusEventReason(event);
if (reason === 'login-started') {
suppressLoginInFlightRef.current = false;
const startedAt = Date.now();
loginStartedAtRef.current = startedAt;
setErrorMessage(null);
@ -303,12 +308,13 @@ export function AmrLoginPill({
stopPolling();
loginStartedAtRef.current = null;
loginPendingRef.current = false;
suppressLoginInFlightRef.current = false;
setPending(null);
setCanceledVisible(false);
setErrorMessage(null);
return;
}
if (next.loginInFlight) {
if (next.loginInFlight && !suppressLoginInFlightRef.current) {
setErrorMessage(null);
setPending('login');
startPolling();
@ -334,6 +340,7 @@ export function AmrLoginPill({
async (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
if (loginPendingRef.current) return;
suppressLoginInFlightRef.current = false;
loginPendingRef.current = true;
const startedAt = Date.now();
loginStartedAtRef.current = startedAt;
@ -379,6 +386,7 @@ export function AmrLoginPill({
configPath: '',
}
));
suppressLoginInFlightRef.current = true;
setPending(null);
setCanceledVisible(true);
notifyAmrLoginStatusChanged('login-canceled');
@ -394,6 +402,7 @@ export function AmrLoginPill({
const result = await velaLogout();
loginStartedAtRef.current = null;
loginPendingRef.current = false;
suppressLoginInFlightRef.current = false;
setPending(null);
if (!result.ok) {
setErrorMessage(t('settings.amrLoginErrorCompact'));
@ -408,7 +417,11 @@ export function AmrLoginPill({
const loggedIn = status?.loggedIn === true;
const userEmail = status?.user?.email ?? '';
const loginInFlight =
pending === 'login' || (status?.loggedIn !== true && status?.loginInFlight === true);
pending === 'login' || (
status?.loggedIn !== true &&
status?.loginInFlight === true &&
!suppressLoginInFlightRef.current
);
const logoutInFlight = pending === 'logout';
const cancelInFlight = pending === 'cancel';
const accountStatus: AmrAccountControlStatus = errorMessage

View file

@ -1812,6 +1812,74 @@ describe('SettingsDialog execution settings Local CLI interactions', () => {
expect(screen.getByText('late@example.com')).toBeTruthy();
});
it('does not resurrect Signing in from a stale loginInFlight echo after cancel', async () => {
let statusStage: 'pending' | 'stale-pending' | 'signed-out' = 'pending';
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
if (url === '/api/memory') {
return new Response(
JSON.stringify({ enabled: true, memories: [], extraction: null }),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
}
if (url === '/api/integrations/vela/status') {
const body =
statusStage === 'signed-out'
? {
loggedIn: false,
loginInFlight: false,
profile: 'local',
user: null,
configPath: '/Users/test/.amr/config.json',
}
: {
loggedIn: false,
loginInFlight: true,
profile: 'local',
user: null,
configPath: '/Users/test/.amr/config.json',
};
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (url === '/api/integrations/vela/login/cancel' && init?.method === 'POST') {
statusStage = 'stale-pending';
return new Response(JSON.stringify({ canceled: true }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
throw new Error(`Unexpected fetch: ${url}`);
});
vi.stubGlobal('fetch', fetchMock);
renderSettingsDialog(
{ mode: 'daemon', agentId: 'amr' },
{ agents: [amrAgent] },
);
fireEvent.click(screen.getByRole('tab', { name: /Local CLI.*1 installed/i }));
const amrCard = screen.getByRole('button', { name: /^Open Design AMR\b/ }).closest('.agent-card') as HTMLElement;
expect(await screen.findByText('Signing in…')).toBeTruthy();
fireEvent.mouseEnter(amrCard);
fireEvent.click(await screen.findByRole('button', { name: 'Cancel' }));
expect(await screen.findByText('Canceled')).toBeTruthy();
window.dispatchEvent(
new CustomEvent('od:amr-login-status-change', {
detail: { reason: 'login-canceled' },
}),
);
await waitFor(() => {
expect(screen.queryByText('Signing in…')).toBeNull();
});
expect(screen.getByRole('button', { name: 'Authorize' })).toBeTruthy();
});
it('renders the signed-in AMR account state inside Settings without leaking vela branding', async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = input.toString();