Enable 'Other' option for yesno question type (#24545)

This commit is contained in:
ruomeng
2026-04-02 15:42:53 -04:00
committed by GitHub
parent 06173c0885
commit c0dfa1aec3
6 changed files with 74 additions and 28 deletions

View File

@@ -1409,6 +1409,53 @@ describe('AskUserDialog', () => {
expect(lastFrame()).toMatchSnapshot();
});
});
it('supports "Other" option for yesno questions', async () => {
const questions: Question[] = [
{
question: 'Is this correct?',
header: 'Confirm',
type: QuestionType.YESNO,
},
];
const onSubmit = vi.fn();
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={onSubmit}
onCancel={vi.fn()}
width={80}
/>,
{ width: 80 },
);
// Navigate to "Other" (3rd option: 1. Yes, 2. No, 3. Other)
writeKey(stdin, '\x1b[B'); // Down to No
writeKey(stdin, '\x1b[B'); // Down to Other
await waitFor(async () => {
await waitUntilReady();
expect(lastFrame()).toContain('Enter a custom value');
});
// Type feedback
for (const char of 'Yes, but with caveats') {
writeKey(stdin, char);
}
await waitFor(async () => {
await waitUntilReady();
expect(lastFrame()).toContain('Yes, but with caveats');
});
// Submit
writeKey(stdin, '\r');
await waitFor(async () => {
expect(onSubmit).toHaveBeenCalledWith({ '0': 'Yes, but with caveats' });
});
});
});
it('expands paste placeholders in multi-select custom option via Done', async () => {

View File

@@ -511,8 +511,9 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
}) => {
const keyMatchers = useKeyMatchers();
const isAlternateBuffer = useAlternateBuffer();
const numOptions =
(question.options?.length ?? 0) + (question.type !== 'yesno' ? 1 : 0);
const hasAll = question.multiSelect && (question.options?.length ?? 0) > 1;
// Calculate total options including 'All' and 'Other' to ensure consistent numbering column width
const numOptions = (question.options?.length ?? 0) + (hasAll ? 1 : 0) + 1;
const numLen = String(numOptions).length;
const radioWidth = 2; // "● "
const numberWidth = numLen + 2; // e.g., "1. "
@@ -735,17 +736,15 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
list.push({ key: 'all', value: allItem });
}
// Only add custom option for choice type, not yesno
if (question.type !== 'yesno') {
const otherItem: OptionItem = {
key: 'other',
label: customOptionText || '',
description: '',
type: 'other',
index: list.length,
};
list.push({ key: 'other', value: otherItem });
}
// Add custom option for choice and yesno types
const otherItem: OptionItem = {
key: 'other',
label: customOptionText || '',
description: '',
type: 'other',
index: list.length,
};
list.push({ key: 'other', value: otherItem });
if (question.multiSelect) {
const doneItem: OptionItem = {
@@ -759,7 +758,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
}
return list;
}, [questionOptions, question.multiSelect, question.type, customOptionText]);
}, [questionOptions, question.multiSelect, customOptionText]);
const handleHighlight = useCallback(
(itemValue: OptionItem) => {

View File

@@ -183,13 +183,13 @@ export enum QuestionType {
export interface Question {
question: string;
header: string;
/** Question type: 'choice' renders selectable options, 'text' renders free-form input, 'yesno' renders a binary Yes/No choice. */
/** Question type: 'choice' renders selectable options, 'text' renders free-form input, 'yesno' renders a Yes/No choice with an optional 'Other' feedback field. */
type: QuestionType;
/** Selectable choices. REQUIRED when type='choice'. IGNORED for 'text' and 'yesno'. */
options?: QuestionOption[];
/** Allow multiple selections. Only applies when type='choice'. */
multiSelect?: boolean;
/** Placeholder hint text. For type='text', shown in the input field. For type='choice', shown in the "Other" custom input. */
/** Placeholder hint text. For type='text', shown in the input field. For type='choice' and 'yesno', shown in the 'Other' custom input. */
placeholder?: string;
/** Allow the question to consume more vertical space instead of being strictly capped. */
unconstrainedHeight?: boolean;

View File

@@ -88,7 +88,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
"type": "boolean",
},
"options": {
"description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
"description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added for 'choice' and 'yesno' types. Not needed for 'text' or 'yesno'.",
"items": {
"properties": {
"description": {
@@ -109,7 +109,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
"type": "array",
},
"placeholder": {
"description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
"description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice' and 'yesno', shown in the 'Other' custom input.",
"type": "string",
},
"question": {
@@ -118,7 +118,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
},
"type": {
"default": "choice",
"description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
"description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation with optional 'Other' feedback.",
"enum": [
"choice",
"text",
@@ -918,7 +918,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
"type": "boolean",
},
"options": {
"description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
"description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added for 'choice' and 'yesno' types. Not needed for 'text' or 'yesno'.",
"items": {
"properties": {
"description": {
@@ -939,7 +939,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
"type": "array",
},
"placeholder": {
"description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
"description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice' and 'yesno', shown in the 'Other' custom input.",
"type": "string",
},
"question": {
@@ -948,7 +948,7 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
},
"type": {
"default": "choice",
"description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
"description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation with optional 'Other' feedback.",
"enum": [
"choice",
"text",

View File

@@ -695,12 +695,12 @@ The agent did not use the todo list because this task could be completed by a ti
enum: ['choice', 'text', 'yesno'],
default: 'choice',
description:
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation with optional 'Other' feedback.",
},
[ASK_USER_QUESTION_PARAM_OPTIONS]: {
type: 'array',
description:
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added for 'choice' and 'yesno' types. Not needed for 'text' or 'yesno'.",
items: {
type: 'object',
required: [
@@ -729,7 +729,7 @@ The agent did not use the todo list because this task could be completed by a ti
[ASK_USER_QUESTION_PARAM_PLACEHOLDER]: {
type: 'string',
description:
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice' and 'yesno', shown in the 'Other' custom input.",
},
},
},

View File

@@ -671,12 +671,12 @@ The agent did not use the todo list because this task could be completed by a ti
enum: ['choice', 'text', 'yesno'],
default: 'choice',
description:
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.",
"Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation with optional 'Other' feedback.",
},
[ASK_USER_QUESTION_PARAM_OPTIONS]: {
type: 'array',
description:
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.",
"The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added for 'choice' and 'yesno' types. Not needed for 'text' or 'yesno'.",
items: {
type: 'object',
required: [
@@ -705,7 +705,7 @@ The agent did not use the todo list because this task could be completed by a ti
[ASK_USER_QUESTION_PARAM_PLACEHOLDER]: {
type: 'string',
description:
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.",
"Hint text shown in the input field. For type='text', shown in the main input. For type='choice' and 'yesno', shown in the 'Other' custom input.",
},
},
},