diff --git a/packages/cli/src/ui/components/AskUserDialog.test.tsx b/packages/cli/src/ui/components/AskUserDialog.test.tsx
index 4f1cca7d8c..5217455358 100644
--- a/packages/cli/src/ui/components/AskUserDialog.test.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.test.tsx
@@ -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(
+ ,
+ { 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 () => {
diff --git a/packages/cli/src/ui/components/AskUserDialog.tsx b/packages/cli/src/ui/components/AskUserDialog.tsx
index 483fcb5055..295d54eb73 100644
--- a/packages/cli/src/ui/components/AskUserDialog.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.tsx
@@ -511,8 +511,9 @@ const ChoiceQuestionView: React.FC = ({
}) => {
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 = ({
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 = ({
}
return list;
- }, [questionOptions, question.multiSelect, question.type, customOptionText]);
+ }, [questionOptions, question.multiSelect, customOptionText]);
const handleHighlight = useCallback(
(itemValue: OptionItem) => {
diff --git a/packages/core/src/confirmation-bus/types.ts b/packages/core/src/confirmation-bus/types.ts
index bb65fbdab7..fb28c01be7 100644
--- a/packages/core/src/confirmation-bus/types.ts
+++ b/packages/core/src/confirmation-bus/types.ts
@@ -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;
diff --git a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap
index 5676b42132..a4790dc188 100644
--- a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap
+++ b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap
@@ -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",
diff --git a/packages/core/src/tools/definitions/model-family-sets/default-legacy.ts b/packages/core/src/tools/definitions/model-family-sets/default-legacy.ts
index dcf9e6e86e..60a52fc6ad 100644
--- a/packages/core/src/tools/definitions/model-family-sets/default-legacy.ts
+++ b/packages/core/src/tools/definitions/model-family-sets/default-legacy.ts
@@ -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.",
},
},
},
diff --git a/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts b/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts
index b69ca43e5a..a86a20378e 100644
--- a/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts
+++ b/packages/core/src/tools/definitions/model-family-sets/gemini-3.ts
@@ -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.",
},
},
},