feat: Display pending and confirming tool calls (#22106)

Co-authored-by: Spencer <spencertang@google.com>
This commit is contained in:
Sri Pasumarthi
2026-03-11 22:57:37 -07:00
committed by GitHub
parent 10ab958378
commit 41d4f59f5e
8 changed files with 96 additions and 21 deletions

View File

@@ -13,6 +13,10 @@ Tips for getting started:
2. /help for more information
3. Ask coding questions, edit code or run commands
4. Be specific for the best results
╭──────────────────────────────────────────────────────────────────────────╮
│ ? confirming_tool Confirming tool description │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
Action Required (was prompted):
@@ -41,6 +45,10 @@ Tips for getting started:
│ ✓ tool2 Description for tool 2 │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────╮
│ o tool3 Description for tool 3 │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -97,6 +105,10 @@ Tips for getting started:
2. /help for more information
3. Ask coding questions, edit code or run commands
4. Be specific for the best results
╭──────────────────────────────────────────────────────────────────────────╮
│ o tool3 Description for tool 3 │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
"
`;

View File

@@ -159,4 +159,22 @@ describe('ThinkingMessage', () => {
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
it('filters out progress dots and empty lines', async () => {
const renderResult = renderWithProviders(
<ThinkingMessage
thought={{ subject: '...', description: 'Thinking\n.\n..\n...\nDone' }}
terminalWidth={80}
isFirstThinking={true}
/>,
);
await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('Thinking');
expect(output).toContain('Done');
expect(renderResult.lastFrame()).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
});

View File

@@ -23,20 +23,26 @@ function normalizeThoughtLines(thought: ThoughtSummary): string[] {
const subject = normalizeEscapedNewlines(thought.subject).trim();
const description = normalizeEscapedNewlines(thought.description).trim();
if (!subject && !description) {
return [];
const isNoise = (text: string) => {
const trimmed = text.trim();
return !trimmed || /^\.+$/.test(trimmed);
};
const lines: string[] = [];
if (subject && !isNoise(subject)) {
lines.push(subject);
}
if (!subject) {
return description.split('\n');
if (description) {
const descriptionLines = description
.split('\n')
.map((line) => line.trim())
.filter((line) => !isNoise(line));
lines.push(...descriptionLines);
}
if (!description) {
return [subject];
}
const bodyLines = description.split('\n');
return [subject, ...bodyLines];
return lines;
}
/**

View File

@@ -118,9 +118,10 @@ describe('<ToolGroupMessage />', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
// Should render nothing because all tools in the group are confirming
// Should now render confirming tools
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
const output = lastFrame();
expect(output).toContain('test-tool');
unmount();
});
@@ -162,11 +163,11 @@ describe('<ToolGroupMessage />', () => {
},
},
);
// pending-tool should be hidden
// pending-tool should now be visible
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).not.toContain('pending-tool');
expect(output).toContain('pending-tool');
expect(output).toContain('error-tool');
expect(output).toMatchSnapshot();
unmount();
@@ -280,12 +281,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
// write_file (Pending) should be hidden
// write_file (Pending) should now be visible
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('read_file');
expect(output).toContain('run_shell_command');
expect(output).not.toContain('write_file');
expect(output).toContain('write_file');
expect(output).toMatchSnapshot();
unmount();
});
@@ -841,7 +842,7 @@ describe('<ToolGroupMessage />', () => {
);
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});

View File

@@ -110,10 +110,11 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
() =>
toolCalls.filter((t) => {
const displayStatus = mapCoreStatusToDisplayStatus(t.status);
return (
displayStatus !== ToolCallStatus.Pending &&
displayStatus !== ToolCallStatus.Confirming
);
// We used to filter out Pending and Confirming statuses here to avoid
// duplication with the Global Queue, but this causes tools to appear to
// "vanish" from the context after approval.
// We now allow them to be visible here as well.
return displayStatus !== ToolCallStatus.Canceled;
}),
[toolCalls],

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs" font-style="italic"> Thinking... </text>
<text x="9" y="19" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="36" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="36" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Thinking</text>
<text x="9" y="53" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="53" fill="#afafaf" textLength="36" lengthAdjust="spacingAndGlyphs" font-style="italic">Done</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

@@ -1,5 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ThinkingMessage > filters out progress dots and empty lines 1`] = `
" Thinking...
│ Thinking
│ Done
"
`;
exports[`ThinkingMessage > filters out progress dots and empty lines 2`] = `
" Thinking...
│ Thinking
│ Done"
`;
exports[`ThinkingMessage > normalizes escaped newline tokens 1`] = `
" Thinking...

View File

@@ -74,6 +74,10 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls incl
│ ⊶ run_shell_command Run command │
│ │
│ Test result │
│ │
│ o write_file Write to file │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -84,6 +88,10 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders multiple tool calls w
│ │
│ Test result │
│ │
│ o pending-tool This tool is pending │
│ │
│ Test result │
│ │
│ x error-tool This tool failed │
│ │
│ Test result │