From bc9b3052ee9a445c630fb9e45133e347b459f816 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 6 Feb 2026 16:40:43 -0800 Subject: [PATCH] fix(cli): reload skills and agents on extension restart (#18411) --- .../src/ui/commands/extensionsCommand.test.ts | 30 +++++++++++++++++++ .../cli/src/ui/commands/extensionsCommand.ts | 12 ++++++++ 2 files changed, 42 insertions(+) diff --git a/packages/cli/src/ui/commands/extensionsCommand.test.ts b/packages/cli/src/ui/commands/extensionsCommand.test.ts index 608dee1942..1e5f395a27 100644 --- a/packages/cli/src/ui/commands/extensionsCommand.test.ts +++ b/packages/cli/src/ui/commands/extensionsCommand.test.ts @@ -129,6 +129,8 @@ describe('extensionsCommand', () => { let mockContext: CommandContext; const mockDispatchExtensionState = vi.fn(); let mockExtensionLoader: unknown; + let mockReloadSkills: MockedFunction<() => Promise>; + let mockReloadAgents: MockedFunction<() => Promise>; beforeEach(() => { vi.resetAllMocks(); @@ -148,12 +150,19 @@ describe('extensionsCommand', () => { mockGetExtensions.mockReturnValue([inactiveExt, activeExt, allExt]); vi.mocked(open).mockClear(); + mockReloadAgents = vi.fn().mockResolvedValue(undefined); + mockReloadSkills = vi.fn().mockResolvedValue(undefined); + mockContext = createMockCommandContext({ services: { config: { getExtensions: mockGetExtensions, getExtensionLoader: vi.fn().mockReturnValue(mockExtensionLoader), getWorkingDir: () => '/test/dir', + reloadSkills: mockReloadSkills, + getAgentRegistry: vi.fn().mockReturnValue({ + reload: mockReloadAgents, + }), }, }, ui: { @@ -892,6 +901,27 @@ describe('extensionsCommand', () => { type: 'RESTARTED', payload: { name: 'ext2' }, }); + expect(mockReloadSkills).toHaveBeenCalled(); + expect(mockReloadAgents).toHaveBeenCalled(); + }); + + it('handles errors during skill or agent reload', async () => { + const mockExtensions = [ + { name: 'ext1', isActive: true }, + ] as GeminiCLIExtension[]; + mockGetExtensions.mockReturnValue(mockExtensions); + mockReloadSkills.mockRejectedValue(new Error('Failed to reload skills')); + + await restartAction!(mockContext, '--all'); + + expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[0]); + expect(mockReloadSkills).toHaveBeenCalled(); + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: MessageType.ERROR, + text: 'Failed to reload skills or agents: Failed to reload skills', + }), + ); }); it('restarts only specified active extensions', async () => { diff --git a/packages/cli/src/ui/commands/extensionsCommand.ts b/packages/cli/src/ui/commands/extensionsCommand.ts index 4cf48d7662..c7359a2a46 100644 --- a/packages/cli/src/ui/commands/extensionsCommand.ts +++ b/packages/cli/src/ui/commands/extensionsCommand.ts @@ -231,6 +231,18 @@ async function restartAction( (result): result is PromiseRejectedResult => result.status === 'rejected', ); + if (failures.length < extensionsToRestart.length) { + try { + await context.services.config?.reloadSkills(); + await context.services.config?.getAgentRegistry()?.reload(); + } catch (error) { + context.ui.addItem({ + type: MessageType.ERROR, + text: `Failed to reload skills or agents: ${getErrorMessage(error)}`, + }); + } + } + if (failures.length > 0) { const errorMessages = failures .map((failure, index) => {