mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-07 20:00:37 -07:00
Re-submission: Make --allowed-tools work in non-interactive mode (#10289)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: matt korwel <matt.korwel@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
ToolConfirmationOutcome,
|
||||
Kind,
|
||||
} from './tools.js';
|
||||
import { ApprovalMode } from '../config/config.js';
|
||||
import { getErrorMessage } from '../utils/errors.js';
|
||||
import { summarizeToolOutput } from '../utils/summarizer.js';
|
||||
import type {
|
||||
@@ -34,11 +35,58 @@ import type { AnsiOutput } from '../utils/terminalSerializer.js';
|
||||
import {
|
||||
getCommandRoots,
|
||||
isCommandAllowed,
|
||||
SHELL_TOOL_NAMES,
|
||||
stripShellWrapper,
|
||||
} from '../utils/shell-utils.js';
|
||||
|
||||
export const OUTPUT_UPDATE_INTERVAL_MS = 1000;
|
||||
|
||||
/**
|
||||
* Parses the `--allowed-tools` flag to determine which sub-commands of the
|
||||
* ShellTool are allowed. The flag can be provided multiple times.
|
||||
*
|
||||
* @param allowedTools The list of allowed tools from the config.
|
||||
* @returns A Set of allowed sub-commands, or null if all commands are allowed.
|
||||
* - `null`: All sub-commands are allowed (e.g., --allowed-tools="ShellTool").
|
||||
* - `Set<string>`: A set of specifically allowed sub-commands (e.g., --allowed-tools="ShellTool(wc)" --allowed-tools="ShellTool(ls)").
|
||||
* - `Set<>` (empty): No sub-commands are allowed (e.g., --allowed-tools="ShellTool()").
|
||||
*/
|
||||
function parseAllowedSubcommands(
|
||||
allowedTools: readonly string[],
|
||||
): Set<string> | null {
|
||||
const shellToolEntries = allowedTools.filter((tool) =>
|
||||
SHELL_TOOL_NAMES.some((name) => tool.startsWith(name)),
|
||||
);
|
||||
|
||||
if (shellToolEntries.length === 0) {
|
||||
return new Set(); // ShellTool not mentioned, so no subcommands are allowed.
|
||||
}
|
||||
|
||||
// If any entry is just "run_shell_command" or "ShellTool", all subcommands are allowed.
|
||||
if (shellToolEntries.some((entry) => SHELL_TOOL_NAMES.includes(entry))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allSubcommands = new Set<string>();
|
||||
const toolNamePattern = SHELL_TOOL_NAMES.join('|');
|
||||
const regex = new RegExp(`^(${toolNamePattern})\\((.*)\\)$`);
|
||||
|
||||
for (const entry of shellToolEntries) {
|
||||
const match = entry.match(regex);
|
||||
if (match) {
|
||||
const subcommands = match[2];
|
||||
if (subcommands) {
|
||||
subcommands
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.forEach((s) => s && allSubcommands.add(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allSubcommands;
|
||||
}
|
||||
|
||||
export interface ShellToolParams {
|
||||
command: string;
|
||||
description?: string;
|
||||
@@ -76,6 +124,30 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
||||
): Promise<ToolCallConfirmationDetails | false> {
|
||||
const command = stripShellWrapper(this.params.command);
|
||||
const rootCommands = [...new Set(getCommandRoots(command))];
|
||||
|
||||
// In non-interactive mode, we need to prevent the tool from hanging while
|
||||
// waiting for user input. If a tool is not fully allowed (e.g. via
|
||||
// --allowed-tools="ShellTool(wc)"), we should throw an error instead of
|
||||
// prompting for confirmation. This check is skipped in YOLO mode.
|
||||
if (
|
||||
!this.config.isInteractive() &&
|
||||
this.config.getApprovalMode() !== ApprovalMode.YOLO
|
||||
) {
|
||||
const allowed = this.config.getAllowedTools() || [];
|
||||
const allowedSubcommands = parseAllowedSubcommands(allowed);
|
||||
if (allowedSubcommands !== null) {
|
||||
// Not all commands are allowed, so we need to check.
|
||||
const allCommandsAllowed = rootCommands.every((cmd) =>
|
||||
allowedSubcommands.has(cmd),
|
||||
);
|
||||
if (!allCommandsAllowed) {
|
||||
throw new Error(
|
||||
`Command "${command}" is not in the list of allowed tools for non-interactive mode.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const commandsToConfirm = rootCommands.filter(
|
||||
(command) => !this.allowlist.has(command),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user