fix(core): deduplicate command names in shell confirmation

Fixes #19768
This commit is contained in:
Sehoon Shon
2026-02-20 19:26:46 -05:00
parent 547f5d45f5
commit 15419434db
2 changed files with 21 additions and 6 deletions
+17 -2
View File
@@ -42,7 +42,7 @@ vi.mock('crypto');
vi.mock('../utils/summarizer.js');
import { initializeShellParsers } from '../utils/shell-utils.js';
import { ShellTool } from './shell.js';
import { ShellTool, OUTPUT_UPDATE_INTERVAL_MS } from './shell.js';
import { debugLogger } from '../index.js';
import { type Config } from '../config/config.js';
import {
@@ -58,7 +58,6 @@ import * as crypto from 'node:crypto';
import * as summarizer from '../utils/summarizer.js';
import { ToolErrorType } from './tool-error.js';
import { ToolConfirmationOutcome } from './tools.js';
import { OUTPUT_UPDATE_INTERVAL_MS } from './shell.js';
import { SHELL_TOOL_NAME } from './tool-names.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
import {
@@ -775,6 +774,22 @@ describe('ShellTool', () => {
});
describe('getConfirmationDetails', () => {
it('should deduplicate root commands in confirmation details', async () => {
const shellTool = new ShellTool(mockConfig, createMockMessageBus());
const command = 'git status && git diff && git log';
const invocation = shellTool.build({ command });
// @ts-expect-error - getConfirmationDetails is protected
const details = await invocation.getConfirmationDetails(
new AbortController().signal,
);
expect(details).not.toBe(false);
if (details && details.type === 'exec') {
expect(details.rootCommand).toBe('git');
}
});
it('should annotate sub-commands with redirection correctly', async () => {
const shellTool = new ShellTool(mockConfig, createMockMessageBus());
const command = 'mkdir -p baz && echo "hello" > baz/test.md && ls';
+4 -4
View File
@@ -16,13 +16,13 @@ import type {
ToolResult,
ToolCallConfirmationDetails,
ToolExecuteConfirmationDetails,
PolicyUpdateOptions,
} from './tools.js';
import {
BaseDeclarativeTool,
BaseToolInvocation,
ToolConfirmationOutcome,
Kind,
type PolicyUpdateOptions,
} from './tools.js';
import { getErrorMessage } from '../utils/errors.js';
@@ -124,9 +124,9 @@ export class ShellToolInvocation extends BaseToolInvocation<
rootCommandDisplay += ', redirection';
}
} else {
rootCommandDisplay = parsed.details
.map((detail) => detail.name)
.join(', ');
rootCommandDisplay = [
...new Set(parsed.details.map((detail) => detail.name)),
].join(', ');
}
const rootCommands = [...new Set(getCommandRoots(command))];