Loading src/common/diagnostics/feature_state_diagnostics/feature_state_diagnostics_renderer.test.ts +15 −1 Original line number Diff line number Diff line Loading @@ -153,7 +153,14 @@ const createMockState = (engaged: boolean): AllFeaturesState => { createCheck('agentic-chat-no-support', 'Not supported'), ], }, [SANDBOX]: { featureId: SANDBOX, engagedChecks: [], allChecks: [] }, [SANDBOX]: { featureId: SANDBOX, engagedChecks: engaged ? [createCheck('sandbox-disabled-by-user' as StateCheckId)] : [], allChecks: [ createCheck('sandbox-disabled-by-user' as StateCheckId), createCheck('sandbox-missing-dependencies' as StateCheckId), ], }, }; }; Loading Loading @@ -221,6 +228,13 @@ describe('FeatureStateDiagnosticsRenderer', () => { expect(result[3].content).toBe(expectedAgenticChat); expect(result[4].title).toBe(`GitLab Flows (${expectedStatus})`); expect(result[4].content).toBe(expectedFlows); const expectedSandboxing = `- ${checkStatusPre} Process sandboxing is enabled in settings ${checkStatusAppend}\n` + `- ${checkStatusPre} System dependencies required for process sandboxing are installed ${checkStatusAppend}`; expect(result[5].title).toBe(`GitLab Duo Agent Sandboxing (${expectedStatus})`); expect(result[5].content).toBe(expectedSandboxing); }, ); Loading src/common/diagnostics/feature_state_diagnostics/feature_state_diagnostics_renderer.ts +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ import { CHAT_TERMINAL_CONTEXT, AGENTIC_CHAT, FLOWS, SANDBOX, } from '@gitlab-org/gitlab-lsp'; import { DiagnosticsRenderer, DiagnosticsSection } from '../diagnostics_service'; import { Loading Loading @@ -70,6 +71,7 @@ export class FeatureStateDiagnosticsRenderer implements DiagnosticsRenderer<[All agentPlatformEnabledCheck, ), createFeatureStateDiagnosticsSection('GitLab Flows', state[FLOWS], agentPlatformEnabledCheck), createFeatureStateDiagnosticsSection('GitLab Duo Agent Sandboxing', state[SANDBOX]), ]; } } src/common/duo_quick_pick/commands/show_quick_pick_menu.test.ts +78 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ import { createFakePartial } from '../../test_utils/create_fake_partial'; import { createFakeWorkspaceConfiguration } from '../../test_utils/vscode_fakes'; import * as utils from '../utils'; import { USER_COMMANDS } from '../../command_names'; import { ENABLE_SANDBOX, DISABLE_SANDBOX } from '../constants'; import { COMMAND_TOGGLE_SANDBOX } from '../../sandbox/commands/toggle_sandbox'; import { showDuoQuickPickMenu } from './show_quick_pick_menu'; describe('show duo quick pick menu command', () => { Loading Loading @@ -235,6 +237,82 @@ describe('show duo quick pick menu command', () => { }); }); describe('sandbox status item', () => { const sandboxItemMock = { label: '$(pass) GitLab Duo Agent Sandboxing: Enabled' }; beforeEach(() => { jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(undefined); }); it('does not show sandbox item when no languageServerFeatureStateProvider is given', () => { expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('shows sandbox item in the status section when provider returns an item', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(sandboxItemMock); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeDefined(); }); it('does not show sandbox item when provider returns undefined (LS not yet reporting)', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(undefined); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('does not show sandbox item when account is missing', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(sandboxItemMock); setupTest({ isMissingAccount: true, visibleState: VisibleCodeSuggestionsState.NO_ACCOUNT }); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('toggles sandbox when the toggle item is selected', async () => { const toggleItem = { label: ENABLE_SANDBOX }; jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(toggleItem); setupTest(); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); const selectionCallback = jest.mocked(mockQuickPick.onDidChangeSelection).mock.calls[0][0]; await selectionCallback([toggleItem]); expect(vscode.commands.executeCommand).toHaveBeenCalledWith(COMMAND_TOGGLE_SANDBOX); expect(mockQuickPick.hide).toHaveBeenCalled(); }); it('does not show sandbox toggle item when provider returns undefined', async () => { jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(undefined); setupTest(); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(ENABLE_SANDBOX)).toBeUndefined(); expect(findItemByLabel(DISABLE_SANDBOX)).toBeUndefined(); }); }); describe('code suggestions language toggle', () => { const toggleItemMock = { label: 'Enable Code Suggestions for javascript' }; Loading src/common/duo_quick_pick/commands/show_quick_pick_menu.ts +18 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ import { generateQuickPickItem, generateDuoUnavailableStatusItem, generateDuoDiagnosticsStatusItem, generateSandboxStatusItem, generateSandboxToggleItem, } from '../utils'; import { CODE_SUGGESTIONS_DISABLED_NOTIFICATION, Loading @@ -26,6 +28,8 @@ import { import { CodeSuggestionsStateManager } from '../../code_suggestions/code_suggestions_state_manager'; import { COMMAND_TOGGLE_CODE_SUGGESTIONS } from '../../code_suggestions/commands/toggle'; import { COMMAND_TOGGLE_CODE_SUGGESTIONS_FOR_LANGUAGE } from '../../code_suggestions/commands/toggle_language'; import { LanguageServerFeatureStateProvider } from '../../language_server/language_server_feature_state_provider'; import { COMMAND_TOGGLE_SANDBOX } from '../../sandbox/commands/toggle_sandbox'; export const SHOW_QUICK_PICK_MENU = 'gl.showDuoQuickPickMenu'; Loading @@ -50,6 +54,7 @@ const openDuoChat = () => { const toggleCodeSuggestions = () => vscode.commands.executeCommand(COMMAND_TOGGLE_CODE_SUGGESTIONS); const toggleCodeSuggestionsForLanguage = () => vscode.commands.executeCommand(COMMAND_TOGGLE_CODE_SUGGESTIONS_FOR_LANGUAGE); const toggleSandbox = () => vscode.commands.executeCommand(COMMAND_TOGGLE_SANDBOX); const openDuoSettings = () => vscode.commands.executeCommand(VS_COMMANDS.OPEN_SETTINGS, GITLAB_WORKFLOW_SETTINGS_ANCHOR); const openDocumentation = () => vscode.env.openExternal(vscode.Uri.parse(DOCUMENTATION_URL)); Loading @@ -58,8 +63,10 @@ const showExtensionLogs = () => vscode.commands.executeCommand(USER_COMMANDS.SHO export const showDuoQuickPickMenu = async ({ stateManager, languageServerFeatureStateProvider, }: { stateManager: CodeSuggestionsStateManager; languageServerFeatureStateProvider?: LanguageServerFeatureStateProvider; }) => { const statusItems: vscode.QuickPickItem[] = []; const toggleItems: vscode.QuickPickItem[] = []; Loading @@ -74,12 +81,20 @@ export const showDuoQuickPickMenu = async ({ const codeSuggestionsLanguageToggleItem = generateCodeSuggestionsLangToggleItem(codeSuggestionsEnabled); const duoDiagnosticsStatusItem = generateDuoDiagnosticsStatusItem(stateManager.getVisibleState()); const sandboxStatusItem = languageServerFeatureStateProvider ? generateSandboxStatusItem(languageServerFeatureStateProvider) : undefined; const sandboxToggleItem = languageServerFeatureStateProvider ? generateSandboxToggleItem(languageServerFeatureStateProvider) : undefined; if (!isMissingAccount) { statusItems.push(codeSuggestionsStatusItem, duoChatStatusItem); if (sandboxStatusItem) statusItems.push(sandboxStatusItem); toggleItems.push( ...[ ...(codeSuggestionsLanguageToggleItem ? [codeSuggestionsLanguageToggleItem] : []), codeSuggestionsToggleItem, ...(sandboxToggleItem ? [sandboxToggleItem] : []), ], ); } else { Loading Loading @@ -127,6 +142,9 @@ export const showDuoQuickPickMenu = async ({ case duoSettingsItem: await openDuoSettings(); break; case sandboxToggleItem: await toggleSandbox(); break; case documentationItem: await openDocumentation(); break; Loading src/common/duo_quick_pick/constants.ts +7 −0 Original line number Diff line number Diff line Loading @@ -26,3 +26,10 @@ export const DUO_CHAT_DISABLED_NOTIFICATION = 'GitLab Duo Chat is currently disa export const GITLAB_WORKFLOW_SETTINGS_ANCHOR = '@ext:gitlab.gitlab-workflow'; export const DUO_STATUS_ZERO_PROBLEMS_DETECTED = 'Status: No problems detected'; export const SANDBOX_STATUS_ENABLED = 'Agent Sandboxing: Enabled'; export const SANDBOX_STATUS_DISABLED = 'Agent Sandboxing: Disabled'; export const SANDBOX_STATUS_UNSUPPORTED = 'Agent Sandboxing: Unsupported'; export const SANDBOX_STATUS_MISSING_DEPS = 'Agent Sandboxing: Missing Dependencies'; export const ENABLE_SANDBOX = 'Enable Agent Sandboxing'; export const DISABLE_SANDBOX = 'Disable Agent Sandboxing'; Loading
src/common/diagnostics/feature_state_diagnostics/feature_state_diagnostics_renderer.test.ts +15 −1 Original line number Diff line number Diff line Loading @@ -153,7 +153,14 @@ const createMockState = (engaged: boolean): AllFeaturesState => { createCheck('agentic-chat-no-support', 'Not supported'), ], }, [SANDBOX]: { featureId: SANDBOX, engagedChecks: [], allChecks: [] }, [SANDBOX]: { featureId: SANDBOX, engagedChecks: engaged ? [createCheck('sandbox-disabled-by-user' as StateCheckId)] : [], allChecks: [ createCheck('sandbox-disabled-by-user' as StateCheckId), createCheck('sandbox-missing-dependencies' as StateCheckId), ], }, }; }; Loading Loading @@ -221,6 +228,13 @@ describe('FeatureStateDiagnosticsRenderer', () => { expect(result[3].content).toBe(expectedAgenticChat); expect(result[4].title).toBe(`GitLab Flows (${expectedStatus})`); expect(result[4].content).toBe(expectedFlows); const expectedSandboxing = `- ${checkStatusPre} Process sandboxing is enabled in settings ${checkStatusAppend}\n` + `- ${checkStatusPre} System dependencies required for process sandboxing are installed ${checkStatusAppend}`; expect(result[5].title).toBe(`GitLab Duo Agent Sandboxing (${expectedStatus})`); expect(result[5].content).toBe(expectedSandboxing); }, ); Loading
src/common/diagnostics/feature_state_diagnostics/feature_state_diagnostics_renderer.ts +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ import { CHAT_TERMINAL_CONTEXT, AGENTIC_CHAT, FLOWS, SANDBOX, } from '@gitlab-org/gitlab-lsp'; import { DiagnosticsRenderer, DiagnosticsSection } from '../diagnostics_service'; import { Loading Loading @@ -70,6 +71,7 @@ export class FeatureStateDiagnosticsRenderer implements DiagnosticsRenderer<[All agentPlatformEnabledCheck, ), createFeatureStateDiagnosticsSection('GitLab Flows', state[FLOWS], agentPlatformEnabledCheck), createFeatureStateDiagnosticsSection('GitLab Duo Agent Sandboxing', state[SANDBOX]), ]; } }
src/common/duo_quick_pick/commands/show_quick_pick_menu.test.ts +78 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ import { createFakePartial } from '../../test_utils/create_fake_partial'; import { createFakeWorkspaceConfiguration } from '../../test_utils/vscode_fakes'; import * as utils from '../utils'; import { USER_COMMANDS } from '../../command_names'; import { ENABLE_SANDBOX, DISABLE_SANDBOX } from '../constants'; import { COMMAND_TOGGLE_SANDBOX } from '../../sandbox/commands/toggle_sandbox'; import { showDuoQuickPickMenu } from './show_quick_pick_menu'; describe('show duo quick pick menu command', () => { Loading Loading @@ -235,6 +237,82 @@ describe('show duo quick pick menu command', () => { }); }); describe('sandbox status item', () => { const sandboxItemMock = { label: '$(pass) GitLab Duo Agent Sandboxing: Enabled' }; beforeEach(() => { jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(undefined); }); it('does not show sandbox item when no languageServerFeatureStateProvider is given', () => { expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('shows sandbox item in the status section when provider returns an item', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(sandboxItemMock); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeDefined(); }); it('does not show sandbox item when provider returns undefined (LS not yet reporting)', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(undefined); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('does not show sandbox item when account is missing', async () => { jest.spyOn(utils, 'generateSandboxStatusItem').mockReturnValue(sandboxItemMock); setupTest({ isMissingAccount: true, visibleState: VisibleCodeSuggestionsState.NO_ACCOUNT }); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(sandboxItemMock.label)).toBeUndefined(); }); it('toggles sandbox when the toggle item is selected', async () => { const toggleItem = { label: ENABLE_SANDBOX }; jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(toggleItem); setupTest(); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); const selectionCallback = jest.mocked(mockQuickPick.onDidChangeSelection).mock.calls[0][0]; await selectionCallback([toggleItem]); expect(vscode.commands.executeCommand).toHaveBeenCalledWith(COMMAND_TOGGLE_SANDBOX); expect(mockQuickPick.hide).toHaveBeenCalled(); }); it('does not show sandbox toggle item when provider returns undefined', async () => { jest.spyOn(utils, 'generateSandboxToggleItem').mockReturnValue(undefined); setupTest(); await showDuoQuickPickMenu({ stateManager: mockStateManager, languageServerFeatureStateProvider: {} as never, }); expect(findItemByLabel(ENABLE_SANDBOX)).toBeUndefined(); expect(findItemByLabel(DISABLE_SANDBOX)).toBeUndefined(); }); }); describe('code suggestions language toggle', () => { const toggleItemMock = { label: 'Enable Code Suggestions for javascript' }; Loading
src/common/duo_quick_pick/commands/show_quick_pick_menu.ts +18 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ import { generateQuickPickItem, generateDuoUnavailableStatusItem, generateDuoDiagnosticsStatusItem, generateSandboxStatusItem, generateSandboxToggleItem, } from '../utils'; import { CODE_SUGGESTIONS_DISABLED_NOTIFICATION, Loading @@ -26,6 +28,8 @@ import { import { CodeSuggestionsStateManager } from '../../code_suggestions/code_suggestions_state_manager'; import { COMMAND_TOGGLE_CODE_SUGGESTIONS } from '../../code_suggestions/commands/toggle'; import { COMMAND_TOGGLE_CODE_SUGGESTIONS_FOR_LANGUAGE } from '../../code_suggestions/commands/toggle_language'; import { LanguageServerFeatureStateProvider } from '../../language_server/language_server_feature_state_provider'; import { COMMAND_TOGGLE_SANDBOX } from '../../sandbox/commands/toggle_sandbox'; export const SHOW_QUICK_PICK_MENU = 'gl.showDuoQuickPickMenu'; Loading @@ -50,6 +54,7 @@ const openDuoChat = () => { const toggleCodeSuggestions = () => vscode.commands.executeCommand(COMMAND_TOGGLE_CODE_SUGGESTIONS); const toggleCodeSuggestionsForLanguage = () => vscode.commands.executeCommand(COMMAND_TOGGLE_CODE_SUGGESTIONS_FOR_LANGUAGE); const toggleSandbox = () => vscode.commands.executeCommand(COMMAND_TOGGLE_SANDBOX); const openDuoSettings = () => vscode.commands.executeCommand(VS_COMMANDS.OPEN_SETTINGS, GITLAB_WORKFLOW_SETTINGS_ANCHOR); const openDocumentation = () => vscode.env.openExternal(vscode.Uri.parse(DOCUMENTATION_URL)); Loading @@ -58,8 +63,10 @@ const showExtensionLogs = () => vscode.commands.executeCommand(USER_COMMANDS.SHO export const showDuoQuickPickMenu = async ({ stateManager, languageServerFeatureStateProvider, }: { stateManager: CodeSuggestionsStateManager; languageServerFeatureStateProvider?: LanguageServerFeatureStateProvider; }) => { const statusItems: vscode.QuickPickItem[] = []; const toggleItems: vscode.QuickPickItem[] = []; Loading @@ -74,12 +81,20 @@ export const showDuoQuickPickMenu = async ({ const codeSuggestionsLanguageToggleItem = generateCodeSuggestionsLangToggleItem(codeSuggestionsEnabled); const duoDiagnosticsStatusItem = generateDuoDiagnosticsStatusItem(stateManager.getVisibleState()); const sandboxStatusItem = languageServerFeatureStateProvider ? generateSandboxStatusItem(languageServerFeatureStateProvider) : undefined; const sandboxToggleItem = languageServerFeatureStateProvider ? generateSandboxToggleItem(languageServerFeatureStateProvider) : undefined; if (!isMissingAccount) { statusItems.push(codeSuggestionsStatusItem, duoChatStatusItem); if (sandboxStatusItem) statusItems.push(sandboxStatusItem); toggleItems.push( ...[ ...(codeSuggestionsLanguageToggleItem ? [codeSuggestionsLanguageToggleItem] : []), codeSuggestionsToggleItem, ...(sandboxToggleItem ? [sandboxToggleItem] : []), ], ); } else { Loading Loading @@ -127,6 +142,9 @@ export const showDuoQuickPickMenu = async ({ case duoSettingsItem: await openDuoSettings(); break; case sandboxToggleItem: await toggleSandbox(); break; case documentationItem: await openDocumentation(); break; Loading
src/common/duo_quick_pick/constants.ts +7 −0 Original line number Diff line number Diff line Loading @@ -26,3 +26,10 @@ export const DUO_CHAT_DISABLED_NOTIFICATION = 'GitLab Duo Chat is currently disa export const GITLAB_WORKFLOW_SETTINGS_ANCHOR = '@ext:gitlab.gitlab-workflow'; export const DUO_STATUS_ZERO_PROBLEMS_DETECTED = 'Status: No problems detected'; export const SANDBOX_STATUS_ENABLED = 'Agent Sandboxing: Enabled'; export const SANDBOX_STATUS_DISABLED = 'Agent Sandboxing: Disabled'; export const SANDBOX_STATUS_UNSUPPORTED = 'Agent Sandboxing: Unsupported'; export const SANDBOX_STATUS_MISSING_DEPS = 'Agent Sandboxing: Missing Dependencies'; export const ENABLE_SANDBOX = 'Enable Agent Sandboxing'; export const DISABLE_SANDBOX = 'Disable Agent Sandboxing';