fix(cli)#21297: clear skills consent dialog before reload (#26431)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
Manav Sharma
2026-05-04 23:23:03 +05:30
committed by GitHub
parent 37edd1d4df
commit 0da1a2026a
5 changed files with 67 additions and 2 deletions
@@ -149,6 +149,35 @@ describe('consent', () => {
expect(consent).toBe(expected);
},
);
it('should clear the active confirmation request before resolving', async () => {
const clearConfirmationRequest = vi.fn();
const steps: string[] = [];
const addExtensionUpdateConfirmationRequest = vi
.fn()
.mockImplementation((request: ConfirmationRequest) => {
steps.push('prompted');
request.onConfirm(true);
steps.push('confirmed');
});
const consentPromise = requestConsentInteractive(
'Test consent',
addExtensionUpdateConfirmationRequest,
() => {
steps.push('cleared');
clearConfirmationRequest();
},
).then((consent) => {
steps.push('resolved');
return consent;
});
expect(clearConfirmationRequest).toHaveBeenCalledTimes(1);
expect(steps).toEqual(['prompted', 'cleared', 'confirmed']);
await expect(consentPromise).resolves.toBe(true);
expect(steps).toEqual(['prompted', 'cleared', 'confirmed', 'resolved']);
});
});
describe('maybeRequestConsentOrFail', () => {
@@ -78,10 +78,12 @@ export async function requestConsentNonInteractive(
export async function requestConsentInteractive(
consentDescription: string,
addExtensionUpdateConfirmationRequest: (value: ConfirmationRequest) => void,
clearConfirmationRequest?: () => void,
): Promise<boolean> {
return promptForConsentInteractive(
consentDescription + '\n\nDo you want to continue?',
addExtensionUpdateConfirmationRequest,
clearConfirmationRequest,
);
}
@@ -129,12 +131,14 @@ export async function promptForConsentNonInteractive(
async function promptForConsentInteractive(
prompt: string,
addExtensionUpdateConfirmationRequest: (value: ConfirmationRequest) => void,
clearConfirmationRequest?: () => void,
): Promise<boolean> {
return new Promise<boolean>((resolve) => {
addExtensionUpdateConfirmationRequest({
prompt,
onConfirm: (resolvedConfirmed) => {
resolve(resolvedConfirmed);
clearConfirmationRequest?.();
setImmediate(() => resolve(resolvedConfirmed));
},
});
});
@@ -37,6 +37,7 @@ vi.mock('../../config/extensions/consent.js', async (importOriginal) => {
});
import { linkSkill } from '../../utils/skillUtils.js';
import { requestConsentInteractive } from '../../config/extensions/consent.js';
vi.mock('../../config/settings.js', async (importOriginal) => {
const actual =
@@ -253,6 +254,36 @@ describe('skillsCommand', () => {
);
});
it('should pass a cleanup callback for interactive workspace consent', async () => {
const linkCmd = skillsCommand.subCommands!.find(
(s) => s.name === 'link',
)!;
context.ui.setConfirmationRequest = vi.fn();
vi.mocked(linkSkill).mockImplementation(
async (_sourcePath, _scope, _addItem, requestConsent) => {
expect(requestConsent).toBeDefined();
await requestConsent!(
[{ name: 'test-skill', location: '/path' } as SkillDefinition],
'/workspace/.gemini/skills',
);
return [{ name: 'test-skill', location: '/path' }];
},
);
await linkCmd.action!(context, '/some/path --scope workspace');
const requestConsentCall = vi
.mocked(requestConsentInteractive)
.mock.calls.at(-1);
expect(requestConsentCall?.[1]).toEqual(expect.any(Function));
const clearConfirmationRequest = requestConsentCall?.[2];
expect(clearConfirmationRequest).toBeTypeOf('function');
clearConfirmationRequest?.();
expect(context.ui.setConfirmationRequest).toHaveBeenCalledWith(null);
});
it('should show error if link fails', async () => {
const linkCmd = skillsCommand.subCommands!.find(
(s) => s.name === 'link',
@@ -118,6 +118,7 @@ async function linkAction(
return requestConsentInteractive(
consentString,
context.ui.setConfirmationRequest.bind(context.ui),
() => context.ui.setConfirmationRequest(null),
);
},
);
+1 -1
View File
@@ -89,7 +89,7 @@ export interface CommandContext {
*
* @param value The confirmation request details.
*/
setConfirmationRequest: (value: ConfirmationRequest) => void;
setConfirmationRequest: (value: ConfirmationRequest | null) => void;
removeComponent: () => void;
toggleBackgroundTasks: () => void;
toggleShortcutsHelp: () => void;