mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat(cli): allow safe tools to execute concurrently while agent is busy (#21988)
This commit is contained in:
@@ -162,6 +162,7 @@ import {
|
|||||||
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
|
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
|
||||||
import { NewAgentsChoice } from './components/NewAgentsNotification.js';
|
import { NewAgentsChoice } from './components/NewAgentsNotification.js';
|
||||||
import { isSlashCommand } from './utils/commandUtils.js';
|
import { isSlashCommand } from './utils/commandUtils.js';
|
||||||
|
import { parseSlashCommand } from '../utils/commands.js';
|
||||||
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
||||||
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
||||||
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
|
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
|
||||||
@@ -1289,6 +1290,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
...pendingGeminiHistoryItems,
|
...pendingGeminiHistoryItems,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (isSlash && isAgentRunning) {
|
||||||
|
const { commandToExecute } = parseSlashCommand(
|
||||||
|
submittedValue,
|
||||||
|
slashCommands ?? [],
|
||||||
|
);
|
||||||
|
if (commandToExecute?.isSafeConcurrent) {
|
||||||
|
void handleSlashCommand(submittedValue);
|
||||||
|
addInput(submittedValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.isModelSteeringEnabled() && isAgentRunning && !isSlash) {
|
if (config.isModelSteeringEnabled() && isAgentRunning && !isSlash) {
|
||||||
handleHintSubmit(submittedValue);
|
handleHintSubmit(submittedValue);
|
||||||
addInput(submittedValue);
|
addInput(submittedValue);
|
||||||
@@ -1332,6 +1345,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
addMessage,
|
addMessage,
|
||||||
addInput,
|
addInput,
|
||||||
submitQuery,
|
submitQuery,
|
||||||
|
handleSlashCommand,
|
||||||
|
slashCommands,
|
||||||
isMcpReady,
|
isMcpReady,
|
||||||
streamingState,
|
streamingState,
|
||||||
messageQueue.length,
|
messageQueue.length,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const aboutCommand: SlashCommand = {
|
|||||||
description: 'Show version info',
|
description: 'Show version info',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const osVersion = process.platform;
|
const osVersion = process.platform;
|
||||||
let sandboxEnv = 'no sandbox';
|
let sandboxEnv = 'no sandbox';
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const settingsCommand: SlashCommand = {
|
|||||||
description: 'View and edit Gemini CLI settings',
|
description: 'View and edit Gemini CLI settings',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: (_context, _args): OpenDialogActionReturn => ({
|
action: (_context, _args): OpenDialogActionReturn => ({
|
||||||
type: 'dialog',
|
type: 'dialog',
|
||||||
dialog: 'settings',
|
dialog: 'settings',
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const statsCommand: SlashCommand = {
|
|||||||
description: 'Check session stats. Usage: /stats [session|model|tools]',
|
description: 'Check session stats. Usage: /stats [session|model|tools]',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: false,
|
autoExecute: false,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: async (context: CommandContext) => {
|
action: async (context: CommandContext) => {
|
||||||
await defaultSessionView(context);
|
await defaultSessionView(context);
|
||||||
},
|
},
|
||||||
@@ -93,6 +94,7 @@ export const statsCommand: SlashCommand = {
|
|||||||
description: 'Show session-specific usage statistics',
|
description: 'Show session-specific usage statistics',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: async (context: CommandContext) => {
|
action: async (context: CommandContext) => {
|
||||||
await defaultSessionView(context);
|
await defaultSessionView(context);
|
||||||
},
|
},
|
||||||
@@ -102,6 +104,7 @@ export const statsCommand: SlashCommand = {
|
|||||||
description: 'Show model-specific usage statistics',
|
description: 'Show model-specific usage statistics',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: (context: CommandContext) => {
|
action: (context: CommandContext) => {
|
||||||
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
|
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
|
||||||
const currentModel = context.services.config?.getModel();
|
const currentModel = context.services.config?.getModel();
|
||||||
@@ -125,6 +128,7 @@ export const statsCommand: SlashCommand = {
|
|||||||
description: 'Show tool-specific usage statistics',
|
description: 'Show tool-specific usage statistics',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: (context: CommandContext) => {
|
action: (context: CommandContext) => {
|
||||||
context.ui.addItem({
|
context.ui.addItem({
|
||||||
type: MessageType.TOOL_STATS,
|
type: MessageType.TOOL_STATS,
|
||||||
|
|||||||
@@ -207,6 +207,11 @@ export interface SlashCommand {
|
|||||||
*/
|
*/
|
||||||
autoExecute?: boolean;
|
autoExecute?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this command can be safely executed while the agent is busy (e.g. streaming a response).
|
||||||
|
*/
|
||||||
|
isSafeConcurrent?: boolean;
|
||||||
|
|
||||||
// Optional metadata for extension commands
|
// Optional metadata for extension commands
|
||||||
extensionName?: string;
|
extensionName?: string;
|
||||||
extensionId?: string;
|
extensionId?: string;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const vimCommand: SlashCommand = {
|
|||||||
description: 'Toggle vim mode on/off',
|
description: 'Toggle vim mode on/off',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
|
isSafeConcurrent: true,
|
||||||
action: async (context, _args) => {
|
action: async (context, _args) => {
|
||||||
const newVimState = await context.ui.toggleVimEnabled();
|
const newVimState = await context.ui.toggleVimEnabled();
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mockSlashCommands: SlashCommand[] = [
|
const mockSlashCommands: SlashCommand[] = [
|
||||||
|
{
|
||||||
|
name: 'stats',
|
||||||
|
description: 'Check stats',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
isSafeConcurrent: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'clear',
|
name: 'clear',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
@@ -3876,6 +3882,13 @@ describe('InputPrompt', () => {
|
|||||||
shouldSubmit: false,
|
shouldSubmit: false,
|
||||||
errorMessage: 'Slash commands cannot be queued',
|
errorMessage: 'Slash commands cannot be queued',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow concurrent-safe slash commands',
|
||||||
|
bufferText: '/stats',
|
||||||
|
shellMode: false,
|
||||||
|
shouldSubmit: true,
|
||||||
|
errorMessage: null,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'should prevent shell commands',
|
name: 'should prevent shell commands',
|
||||||
bufferText: 'ls',
|
bufferText: 'ls',
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import {
|
|||||||
isAutoExecutableCommand,
|
isAutoExecutableCommand,
|
||||||
isSlashCommand,
|
isSlashCommand,
|
||||||
} from '../utils/commandUtils.js';
|
} from '../utils/commandUtils.js';
|
||||||
|
import { parseSlashCommand } from '../../utils/commands.js';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
|
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
|
||||||
import { getSafeLowColorBackground } from '../themes/color-utils.js';
|
import { getSafeLowColorBackground } from '../themes/color-utils.js';
|
||||||
@@ -408,6 +409,17 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
(isSlash || isShell) &&
|
(isSlash || isShell) &&
|
||||||
streamingState === StreamingState.Responding
|
streamingState === StreamingState.Responding
|
||||||
) {
|
) {
|
||||||
|
if (isSlash) {
|
||||||
|
const { commandToExecute } = parseSlashCommand(
|
||||||
|
trimmedMessage,
|
||||||
|
slashCommands,
|
||||||
|
);
|
||||||
|
if (commandToExecute?.isSafeConcurrent) {
|
||||||
|
inputHistory.handleSubmit(trimmedMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setQueueErrorMessage(
|
setQueueErrorMessage(
|
||||||
`${isShell ? 'Shell' : 'Slash'} commands cannot be queued`,
|
`${isShell ? 'Shell' : 'Slash'} commands cannot be queued`,
|
||||||
);
|
);
|
||||||
@@ -415,7 +427,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
}
|
}
|
||||||
inputHistory.handleSubmit(trimmedMessage);
|
inputHistory.handleSubmit(trimmedMessage);
|
||||||
},
|
},
|
||||||
[inputHistory, shellModeActive, streamingState, setQueueErrorMessage],
|
[
|
||||||
|
inputHistory,
|
||||||
|
shellModeActive,
|
||||||
|
streamingState,
|
||||||
|
setQueueErrorMessage,
|
||||||
|
slashCommands,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Effect to reset completion if history navigation just occurred and set the text
|
// Effect to reset completion if history navigation just occurred and set the text
|
||||||
|
|||||||
Reference in New Issue
Block a user