feat: human-readable permission labels, deny rule feedback, and multi-dir search tests

- Add buildHumanReadableRuleLabel() to convert raw permission rules into
  natural-language descriptions for the 'Always Allow' UI options
- Add PermissionManager.findMatchingDenyRule() to surface which deny rule
  caused a tool to be blocked, improving error messages in coreToolScheduler
- Update ToolConfirmationMessage to use friendly labels with i18n support
- Add comprehensive tests for new permission features and multi-directory
  search in glob, grep, and ripGrep tools
- Fix integration test for tool-control allowedTools configuration
This commit is contained in:
LaZzyMan 2026-03-24 19:47:07 +08:00
parent 8c31775573
commit a5a8ec5d67
14 changed files with 622 additions and 19 deletions

View file

@ -17,7 +17,11 @@ import type {
Config,
EditorType,
} from '@qwen-code/qwen-code-core';
import { IdeClient, ToolConfirmationOutcome } from '@qwen-code/qwen-code-core';
import {
IdeClient,
ToolConfirmationOutcome,
buildHumanReadableRuleLabel,
} from '@qwen-code/qwen-code-core';
import type { RadioSelectItem } from '../shared/RadioButtonSelect.js';
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
@ -243,16 +247,24 @@ export const ToolConfirmationMessage: React.FC<
key: 'Yes, allow once',
});
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel = executionProps.permissionRules?.length
? ` [${executionProps.permissionRules.join(', ')}]`
const friendlyLabel = executionProps.permissionRules?.length
? ` ${buildHumanReadableRuleLabel(executionProps.permissionRules)}`
: '';
options.push({
label: t('Always allow in this project') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} in this project', {
action: friendlyLabel.trim(),
})
: t('Always allow in this project'),
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Always allow for this user') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} for this user', {
action: friendlyLabel.trim(),
})
: t('Always allow for this user'),
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});
@ -324,18 +336,26 @@ export const ToolConfirmationMessage: React.FC<
key: 'Yes, allow once',
});
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel =
const friendlyLabel =
'permissionRules' in infoProps &&
(infoProps as { permissionRules?: string[] }).permissionRules?.length
? ` [${(infoProps as { permissionRules?: string[] }).permissionRules!.join(', ')}]`
? ` ${buildHumanReadableRuleLabel((infoProps as { permissionRules?: string[] }).permissionRules!)}`
: '';
options.push({
label: t('Always allow in this project') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} in this project', {
action: friendlyLabel.trim(),
})
: t('Always allow in this project'),
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Always allow for this user') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} for this user', {
action: friendlyLabel.trim(),
})
: t('Always allow for this user'),
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});
@ -401,16 +421,24 @@ export const ToolConfirmationMessage: React.FC<
key: 'Yes, allow once',
});
if (isTrustedFolder && !confirmationDetails.hideAlwaysAllow) {
const rulesLabel = mcpProps.permissionRules?.length
? ` [${mcpProps.permissionRules.join(', ')}]`
const friendlyLabel = mcpProps.permissionRules?.length
? ` ${buildHumanReadableRuleLabel(mcpProps.permissionRules)}`
: '';
options.push({
label: t('Always allow in this project') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} in this project', {
action: friendlyLabel.trim(),
})
: t('Always allow in this project'),
value: ToolConfirmationOutcome.ProceedAlwaysProject,
key: 'Always allow in this project',
});
options.push({
label: t('Always allow for this user') + rulesLabel,
label: friendlyLabel
? t('Always allow {{action}} for this user', {
action: friendlyLabel.trim(),
})
: t('Always allow for this user'),
value: ToolConfirmationOutcome.ProceedAlwaysUser,
key: 'Always allow for this user',
});