fix: prevent duplicate tool confirmations and fragmented UI boxes

- Process tool validations sequentially rather than concurrently in the
  scheduler. This resolves a race condition where parallel tool calls
  evaluated policy simultaneously, causing subsequent tools to ignore
  global `AUTO_EDIT` mode switches triggered by earlier confirmations.
- Conditionally render result display containers in `ToolMessage` and
  `ShellToolMessage`. Empty padded boxes are now hidden when a tool is
  pending or executing with no output, preventing overlapping disjointed
  borders from fragmenting the interactive queue.
This commit is contained in:
Sri Pasumarthi
2026-03-17 17:54:55 -07:00
parent b8719bcd47
commit 813466feb1
4 changed files with 82 additions and 87 deletions
@@ -18,20 +18,8 @@ Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
"
`;
exports[`ConfigInitDisplay > truncates list of waiting servers if too many 2`] = `
"
Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
"
`;
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
"
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
"
`;
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 2`] = `
"
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
"
`;
@@ -64,12 +64,10 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
isFirst,
borderColor,
borderDimColor,
isExpandable,
originalRequestName,
progress,
}) => {
const {
activePtyId: activeShellPtyId,
@@ -196,35 +194,39 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
{emphasis === 'high' && <TrailingIndicator />}
</StickyHeader>
<Box
ref={contentRef}
width={terminalWidth}
borderStyle="round"
borderColor={borderColor}
borderDimColor={borderDimColor}
borderTop={false}
borderBottom={false}
borderLeft={true}
borderRight={true}
paddingX={1}
flexDirection="column"
>
<ToolResultDisplay
resultDisplay={resultDisplay}
availableTerminalHeight={availableTerminalHeight}
terminalWidth={terminalWidth}
renderOutputAsMarkdown={renderOutputAsMarkdown}
hasFocus={isThisShellFocused}
maxLines={maxLines}
/>
{isThisShellFocused && config && (
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
scrollPageSize={availableTerminalHeight ?? ACTIVE_SHELL_MAX_LINES}
{(!!resultDisplay ||
(status === CoreToolCallStatus.Executing && progress !== undefined) ||
(isThisShellFocused && !!config)) && (
<Box
ref={contentRef}
width={terminalWidth}
borderStyle="round"
borderColor={borderColor}
borderDimColor={borderDimColor}
borderTop={false}
borderBottom={false}
borderLeft={true}
borderRight={true}
paddingX={1}
flexDirection="column"
>
<ToolResultDisplay
resultDisplay={resultDisplay}
availableTerminalHeight={availableTerminalHeight}
terminalWidth={terminalWidth}
renderOutputAsMarkdown={renderOutputAsMarkdown}
hasFocus={isThisShellFocused}
maxLines={maxLines}
/>
)}
</Box>
{isThisShellFocused && config && (
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
scrollPageSize={availableTerminalHeight ?? ACTIVE_SHELL_MAX_LINES}
/>
)}
</Box>
)}
</>
);
};
@@ -109,48 +109,53 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
/>
{emphasis === 'high' && <TrailingIndicator />}
</StickyHeader>
<Box
width={terminalWidth}
borderStyle="round"
borderColor={borderColor}
borderDimColor={borderDimColor}
borderTop={false}
borderBottom={false}
borderLeft={true}
borderRight={true}
paddingX={1}
flexDirection="column"
>
{status === CoreToolCallStatus.Executing && progress !== undefined && (
<McpProgressIndicator
progress={progress}
total={progressTotal}
message={progressMessage}
barWidth={20}
{(!!resultDisplay ||
(status === CoreToolCallStatus.Executing && progress !== undefined) ||
(isThisShellFocused && !!config)) && (
<Box
width={terminalWidth}
borderStyle="round"
borderColor={borderColor}
borderDimColor={borderDimColor}
borderTop={false}
borderBottom={false}
borderLeft={true}
borderRight={true}
paddingX={1}
flexDirection="column"
>
{status === CoreToolCallStatus.Executing &&
progress !== undefined && (
<McpProgressIndicator
progress={progress}
total={progressTotal}
message={progressMessage}
barWidth={20}
/>
)}
<ToolResultDisplay
resultDisplay={resultDisplay}
availableTerminalHeight={availableTerminalHeight}
terminalWidth={terminalWidth}
renderOutputAsMarkdown={renderOutputAsMarkdown}
hasFocus={isThisShellFocused}
maxLines={
kind === Kind.Agent && availableTerminalHeight !== undefined
? SUBAGENT_MAX_LINES
: undefined
}
overflowDirection={kind === Kind.Agent ? 'bottom' : 'top'}
/>
)}
<ToolResultDisplay
resultDisplay={resultDisplay}
availableTerminalHeight={availableTerminalHeight}
terminalWidth={terminalWidth}
renderOutputAsMarkdown={renderOutputAsMarkdown}
hasFocus={isThisShellFocused}
maxLines={
kind === Kind.Agent && availableTerminalHeight !== undefined
? SUBAGENT_MAX_LINES
: undefined
}
overflowDirection={kind === Kind.Agent ? 'bottom' : 'top'}
/>
{isThisShellFocused && config && (
<Box paddingLeft={STATUS_INDICATOR_WIDTH} marginTop={1}>
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
/>
</Box>
)}
</Box>
{isThisShellFocused && config && (
<Box paddingLeft={STATUS_INDICATOR_WIDTH} marginTop={1}>
<ShellInputPrompt
activeShellPtyId={activeShellPtyId ?? null}
focus={embeddedShellFocused}
/>
</Box>
)}
</Box>
)}
</>
);
};
+3 -3
View File
@@ -455,9 +455,9 @@ export class Scheduler {
c.status === CoreToolCallStatus.Validating,
);
if (validatingCalls.length > 0) {
await Promise.all(
validatingCalls.map((c) => this._processValidatingCall(c, signal)),
);
for (const c of validatingCalls) {
await this._processValidatingCall(c, signal);
}
}
// 2. Execute scheduled calls