Skip to content
22 changes: 18 additions & 4 deletions packages/cli/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
WRITE_FILE_TOOL_NAME,
EDIT_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
ASK_USER_TOOL_NAME,
type ExtensionLoader,
debugLogger,
ApprovalMode,
Expand Down Expand Up @@ -1014,7 +1015,9 @@ describe('mergeExcludeTools', () => {
process.argv = ['node', 'script.js', '-p', 'test'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getExcludeTools()).toEqual(defaultExcludes);
expect(config.getExcludeTools()).toEqual(
new Set([...defaultExcludes, ASK_USER_TOOL_NAME]),
);
});

it('should handle settings with excludeTools but no extensions', async () => {
Expand Down Expand Up @@ -1098,6 +1101,7 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).toContain(SHELL_TOOL_NAME);
expect(excludedTools).toContain(EDIT_TOOL_NAME);
expect(excludedTools).toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should exclude all interactive tools in non-interactive mode with explicit default approval mode', async () => {
Expand All @@ -1118,6 +1122,7 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).toContain(SHELL_TOOL_NAME);
expect(excludedTools).toContain(EDIT_TOOL_NAME);
expect(excludedTools).toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should exclude only shell tools in non-interactive mode with auto_edit approval mode', async () => {
Expand All @@ -1138,9 +1143,10 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).toContain(SHELL_TOOL_NAME);
expect(excludedTools).not.toContain(EDIT_TOOL_NAME);
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should exclude no interactive tools in non-interactive mode with yolo approval mode', async () => {
it('should exclude only ask_user in non-interactive mode with yolo approval mode', async () => {
process.argv = [
'node',
'script.js',
Expand All @@ -1158,6 +1164,7 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).not.toContain(SHELL_TOOL_NAME);
expect(excludedTools).not.toContain(EDIT_TOOL_NAME);
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should exclude all interactive tools in non-interactive mode with plan approval mode', async () => {
Expand All @@ -1182,9 +1189,10 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).toContain(SHELL_TOOL_NAME);
expect(excludedTools).toContain(EDIT_TOOL_NAME);
expect(excludedTools).toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should exclude no interactive tools in non-interactive mode with legacy yolo flag', async () => {
it('should exclude only ask_user in non-interactive mode with legacy yolo flag', async () => {
process.argv = ['node', 'script.js', '--yolo', '-p', 'test'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings();
Expand All @@ -1195,6 +1203,7 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).not.toContain(SHELL_TOOL_NAME);
expect(excludedTools).not.toContain(EDIT_TOOL_NAME);
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).toContain(ASK_USER_TOOL_NAME);
});

it('should not exclude interactive tools in interactive mode regardless of approval mode', async () => {
Expand All @@ -1219,6 +1228,7 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).not.toContain(SHELL_TOOL_NAME);
expect(excludedTools).not.toContain(EDIT_TOOL_NAME);
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME);
expect(excludedTools).not.toContain(ASK_USER_TOOL_NAME);
}
});

Expand Down Expand Up @@ -1777,6 +1787,7 @@ describe('loadCliConfig tool exclusions', () => {
expect(config.getExcludeTools()).not.toContain('run_shell_command');
expect(config.getExcludeTools()).not.toContain('replace');
expect(config.getExcludeTools()).not.toContain('write_file');
expect(config.getExcludeTools()).not.toContain('ask_user');
});

it('should not exclude interactive tools in interactive mode with YOLO', async () => {
Expand All @@ -1791,6 +1802,7 @@ describe('loadCliConfig tool exclusions', () => {
expect(config.getExcludeTools()).not.toContain('run_shell_command');
expect(config.getExcludeTools()).not.toContain('replace');
expect(config.getExcludeTools()).not.toContain('write_file');
expect(config.getExcludeTools()).not.toContain('ask_user');
});

it('should exclude interactive tools in non-interactive mode without YOLO', async () => {
Expand All @@ -1805,9 +1817,10 @@ describe('loadCliConfig tool exclusions', () => {
expect(config.getExcludeTools()).toContain('run_shell_command');
expect(config.getExcludeTools()).toContain('replace');
expect(config.getExcludeTools()).toContain('write_file');
expect(config.getExcludeTools()).toContain('ask_user');
});

it('should not exclude interactive tools in non-interactive mode with YOLO', async () => {
it('should exclude only ask_user in non-interactive mode with YOLO', async () => {
process.stdin.isTTY = false;
process.argv = ['node', 'script.js', '-p', 'test', '--yolo'];
const argv = await parseArguments(createTestMergedSettings());
Expand All @@ -1819,6 +1832,7 @@ describe('loadCliConfig tool exclusions', () => {
expect(config.getExcludeTools()).not.toContain('run_shell_command');
expect(config.getExcludeTools()).not.toContain('replace');
expect(config.getExcludeTools()).not.toContain('write_file');
expect(config.getExcludeTools()).toContain('ask_user');
});

it('should not exclude shell tool in non-interactive mode when --allowed-tools="ShellTool" is set', async () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
debugLogger,
loadServerHierarchicalMemory,
WEB_FETCH_TOOL_NAME,
ASK_USER_TOOL_NAME,
getVersion,
PREVIEW_GEMINI_MODEL_AUTO,
type HookDefinition,
Expand Down Expand Up @@ -596,6 +597,10 @@ export async function loadCliConfig(
// In non-interactive mode, exclude tools that require a prompt.
const extraExcludes: string[] = [];
if (!interactive) {
// ask_user requires user interaction and must be excluded in all
// non-interactive modes, regardless of the approval mode.
extraExcludes.push(ASK_USER_TOOL_NAME);

Comment thread
jerop marked this conversation as resolved.
const defaultExcludes = [
SHELL_TOOL_NAME,
EDIT_TOOL_NAME,
Expand Down
Loading