Merge remote-tracking branch 'origin/main' into fix/pr2371-btw-complete

# Conflicts:
#	packages/cli/src/ui/AppContainer.tsx
#	packages/cli/src/ui/hooks/useGeminiStream.ts
#	packages/cli/src/ui/layouts/DefaultAppLayout.tsx
#	packages/cli/src/ui/types.ts
#	packages/core/src/core/client.test.ts
This commit is contained in:
yiliang114 2026-03-20 00:55:29 +08:00
commit bd77eef46f
406 changed files with 55514 additions and 6431 deletions

View file

@ -75,7 +75,7 @@ export const SuccessMessage: React.FC<StatusTextProps> = ({ text }) => (
export const WarningMessage: React.FC<StatusTextProps> = ({ text }) => (
<StatusMessage
text={text}
prefix=""
prefix=""
prefixColor={theme.status.warning}
textColor={theme.status.warning}
/>

View file

@ -138,17 +138,17 @@ describe('ToolConfirmationMessage', () => {
{
description: 'for exec confirmations',
details: execConfirmationDetails,
alwaysAllowText: 'Yes, allow always',
alwaysAllowText: 'Always allow in this project',
},
{
description: 'for info confirmations',
details: infoConfirmationDetails,
alwaysAllowText: 'Yes, allow always',
alwaysAllowText: 'Always allow in this project',
},
{
description: 'for mcp confirmations',
details: mcpConfirmationDetails,
alwaysAllowText: 'always allow',
alwaysAllowText: 'Always allow in this project',
},
])('$description', ({ details, alwaysAllowText }) => {
it('should show "allow always" when folder is trusted', () => {

View file

@ -242,11 +242,19 @@ export const ToolConfirmationMessage: React.FC<
value: ToolConfirmationOutcome.ProceedOnce,
key: 'Yes, allow once',
});
if (isTrustedFolder) {
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel = executionProps.permissionRules?.length
? ` [${executionProps.permissionRules.join(', ')}]`
: '';
options.push({
label: t('Yes, allow always ...'),
value: ToolConfirmationOutcome.ProceedAlways,
key: 'Yes, allow always ...',
label: t('Always allow in this project') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Always allow for this user') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});
}
options.push({
@ -315,11 +323,21 @@ export const ToolConfirmationMessage: React.FC<
value: ToolConfirmationOutcome.ProceedOnce,
key: 'Yes, allow once',
});
if (isTrustedFolder) {
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel =
'permissionRules' in infoProps &&
(infoProps as { permissionRules?: string[] }).permissionRules?.length
? ` [${(infoProps as { permissionRules?: string[] }).permissionRules!.join(', ')}]`
: '';
options.push({
label: t('Yes, allow always'),
value: ToolConfirmationOutcome.ProceedAlways,
key: 'Yes, allow always',
label: t('Always allow in this project') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Always allow for this user') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});
}
options.push({
@ -382,21 +400,19 @@ export const ToolConfirmationMessage: React.FC<
value: ToolConfirmationOutcome.ProceedOnce,
key: 'Yes, allow once',
});
if (isTrustedFolder) {
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel = mcpProps.permissionRules?.length
? ` [${mcpProps.permissionRules.join(', ')}]`
: '';
options.push({
label: t('Yes, always allow tool "{{tool}}" from server "{{server}}"', {
tool: mcpProps.toolName,
server: mcpProps.serverName,
}),
value: ToolConfirmationOutcome.ProceedAlwaysTool, // Cast until types are updated
key: `Yes, always allow tool "${mcpProps.toolName}" from server "${mcpProps.serverName}"`,
label: t('Always allow in this project') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Yes, always allow all tools from server "{{server}}"', {
server: mcpProps.serverName,
}),
value: ToolConfirmationOutcome.ProceedAlwaysServer,
key: `Yes, always allow all tools from server "${mcpProps.serverName}"`,
label: t('Always allow for this user') + rulesLabel,
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});
}
options.push({

View file

@ -6,7 +6,7 @@
import type React from 'react';
import { useMemo } from 'react';
import { Box, Text } from 'ink';
import { Box } from 'ink';
import type { IndividualToolCallDisplay } from '../../types.js';
import { ToolCallStatus } from '../../types.js';
import { ToolMessage } from './ToolMessage.js';
@ -136,13 +136,6 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
contentWidth={innerWidth}
/>
)}
{tool.outputFile && (
<Box marginX={1}>
<Text color={theme.text.primary}>
Output too long and was saved to: {tool.outputFile}
</Text>
</Box>
)}
</Box>
);
})}

View file

@ -300,4 +300,55 @@ describe('<ToolMessage />', () => {
);
expect(lastFrame()).toContain('MockAnsiOutput:hello');
});
it('renders rejected plan content with plan text still visible', () => {
const planResultDisplay = {
type: 'plan_summary' as const,
message: 'Plan was rejected. Remaining in plan mode.',
plan: '# My Plan\n- Step 1: Do something\n- Step 2: Do another thing',
rejected: true,
};
const { lastFrame } = renderWithContext(
<ToolMessage
{...baseProps}
name="ExitPlanMode"
description="Plan:"
status={ToolCallStatus.Canceled}
resultDisplay={planResultDisplay}
/>,
StreamingState.Idle,
);
const output = lastFrame();
expect(output).toContain('Plan was rejected. Remaining in plan mode.');
expect(output).toContain('MockMarkdown:# My Plan');
expect(output).toContain('- Step 1: Do something');
expect(output).toContain('- Step 2: Do another thing');
});
it('renders approved plan content with approval message', () => {
const planResultDisplay = {
type: 'plan_summary' as const,
message: 'User approved the plan.',
plan: '# My Plan\n- Step 1\n- Step 2',
};
const { lastFrame } = renderWithContext(
<ToolMessage
{...baseProps}
name="ExitPlanMode"
description="Plan:"
status={ToolCallStatus.Success}
resultDisplay={planResultDisplay}
/>,
StreamingState.Idle,
);
const output = lastFrame();
expect(output).toContain('User approved the plan.');
expect(output).toContain('MockMarkdown:# My Plan');
expect(output).toContain('- Step 1');
expect(output).toContain('- Step 2');
});
});