Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7ea247
feat: enhance ask_followup_question to support multiple questions
ScDor Jan 16, 2026
08aa544
Fix multi-question UX: only send responses when Finish is clicked
ScDor Jan 17, 2026
5521e0d
feat: implement multi-question support with options and UI enhancements
ScDor Jan 31, 2026
d3028c3
Merge origin/main into vk/6325-multi-question and resolve conflicts
ScDor Jan 31, 2026
5393c2b
fix: resolve automated review issues for ask_followup_question
ScDor Feb 5, 2026
08d7bfa
fix: revert unwanted changes in zh-TW README
ScDor Feb 5, 2026
621ab32
chore: merge origin/main into vk/6325-multi-question and resolve conf…
ScDor Feb 9, 2026
4ca2ded
Merge remote-tracking branch 'fork/vk/6325-multi-question' into vk/63…
ScDor Feb 9, 2026
07b7219
docs(quick-1): check branch with PR, fix build failures, pull from ma…
ScDor Feb 9, 2026
c9b6ffd
fix: add missing translations for multi-question feature
ScDor Feb 9, 2026
782caa6
fix: address PR feedback and fix unit test failures
ScDor Feb 9, 2026
c947059
chore: merge origin/main into vk/6325-multi-question and resolve conf…
ScDor Feb 9, 2026
938ae63
chore: resolve remaining conflicts and wire taskHeaderHighlightEnabled
ScDor Feb 9, 2026
06b957a
chore: remove internal planning and documentation files
ScDor Feb 10, 2026
30ae16c
chore: revert unintended documentation and UI changes
ScDor Feb 10, 2026
b3eb2ab
chore: update translations for multi-question and task header highlig…
ScDor Feb 10, 2026
5c5811c
Update webview-ui/src/i18n/locales/id/settings.json
ScDor Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions apps/web-roo-code/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,7 @@ export default function PricingPage() {
<li>To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)</li>
<li>
To pay for AI model inference costs (
<a
href="/provider"
target="_blank"
rel="noopener noreferrer"
className="underline">
<a href="/provider" target="_blank" rel="noopener noreferrer" className="underline">
varies by model
</a>
)
Expand Down
2 changes: 1 addition & 1 deletion locales/zh-TW/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions packages/types/src/followup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { z } from "zod"
export interface FollowUpData {
/** The question being asked by the LLM */
question?: string
/** Array of questions being asked by the LLM */
questions?: FollowUpQuestion[]
/** Array of suggested answers that the user can select */
suggest?: Array<SuggestionItem>
}
Expand All @@ -22,6 +24,12 @@ export interface SuggestionItem {
mode?: string
}

/**
* Type definition for a follow-up question
* Can be a simple string or an object with text and options
*/
export type FollowUpQuestion = string | { text: string; options?: string[] }

/**
* Zod schema for SuggestionItem
*/
Expand All @@ -30,11 +38,23 @@ export const suggestionItemSchema = z.object({
mode: z.string().optional(),
})

/**
* Zod schema for FollowUpQuestion
*/
export const followUpQuestionSchema = z.union([
z.string(),
z.object({
text: z.string(),
options: z.array(z.string()).optional(),
}),
])

/**
* Zod schema for FollowUpData
*/
export const followUpDataSchema = z.object({
question: z.string().optional(),
questions: z.array(followUpQuestionSchema).optional(),
suggest: z.array(suggestionItemSchema).optional(),
})

Expand Down
14 changes: 14 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,18 @@ export const globalSettingsSchema = z.object({
lastTaskExportPath: z.string().optional(),
lastImageSavePath: z.string().optional(),

/**
* Whether to show multiple questions one by one or all at once.
* @default false (all at once)
*/
showQuestionsOneByOne: z.boolean().optional(),

/**
* Whether to highlight the task header in the chat view.
* @default false
*/
taskHeaderHighlightEnabled: z.boolean().optional(),

/**
* Path to worktree to auto-open after switching workspaces.
* Used by the worktree feature to open the Roo Code sidebar in a new window.
Expand Down Expand Up @@ -391,6 +403,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
mode: "code", // "architect",

customModes: [],
showQuestionsOneByOne: false,
taskHeaderHighlightEnabled: false,
}

export const EVALS_TIMEOUT = 5 * 60 * 1_000
2 changes: 2 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export type ExtensionState = Pick<
| "ttsSpeed"
| "soundEnabled"
| "soundVolume"
| "taskHeaderHighlightEnabled"
| "terminalOutputPreviewSize"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
Expand Down Expand Up @@ -313,6 +314,7 @@ export type ExtensionState = Pick<
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
| "showQuestionsOneByOne"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showQuestionsOneByOne is added to ExtensionState here, but ClineProvider.ts never includes it in the state objects returned by getState() or getStateToPostToWebview(). Every other UI setting in this Pick type (e.g., enterBehavior, includeCurrentCost, taskHeaderHighlightEnabled) is explicitly included in both methods. Without the same wiring, the setting value stored via contextProxy.setValue is never sent back to the webview -- it will always default to false on initial load and after webview reloads, and the ExtensionStateContext check at line 343 ((newState as any).showQuestionsOneByOne) will never trigger. You need to add showQuestionsOneByOne to the destructured values from getState() (~line 2082), and include showQuestionsOneByOne: showQuestionsOneByOne ?? false in both state return objects (near lines 2228 and 2470).

Fix it with Roo Code or mention @roomote and request a fix.

| "maxGitStatusFiles"
| "requestDelaySeconds"
| "showWorktreesInHomeScreen"
Expand Down
8 changes: 4 additions & 4 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,9 @@ export class NativeToolCallParser {
break

case "ask_followup_question":
if (partialArgs.question !== undefined || partialArgs.follow_up !== undefined) {
if (partialArgs.questions !== undefined || partialArgs.follow_up !== undefined) {
nativeArgs = {
question: partialArgs.question,
questions: Array.isArray(partialArgs.questions) ? partialArgs.questions : undefined,
follow_up: Array.isArray(partialArgs.follow_up) ? partialArgs.follow_up : undefined,
}
}
Expand Down Expand Up @@ -816,9 +816,9 @@ export class NativeToolCallParser {
break

case "ask_followup_question":
if (args.question !== undefined && args.follow_up !== undefined) {
if (args.questions !== undefined || args.follow_up !== undefined) {
nativeArgs = {
question: args.question,
questions: Array.isArray(args.questions) ? args.questions : undefined,
follow_up: args.follow_up,
} as NativeArgsFor<TName>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () =
closeBrowser: vi.fn().mockResolvedValue(undefined),
},
recordToolUsage: vi.fn(),
recordToolError: vi.fn(),
sayAndCreateMissingParamError: vi.fn().mockResolvedValue("mock error message"),
toolRepetitionDetector: {
check: vi.fn().mockReturnValue({ allowExecution: true }),
},
Expand Down Expand Up @@ -85,8 +87,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () =
type: "tool_use",
id: toolCallId, // ID indicates native tool calling
name: "ask_followup_question",
params: { question: "What do you see?" },
nativeArgs: { question: "What do you see?", follow_up: [] },
params: { questions: ["What do you see?"] },
nativeArgs: { questions: ["What do you see?"], follow_up: [] },
},
]

Expand Down Expand Up @@ -138,8 +140,8 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () =
type: "tool_use",
id: toolCallId,
name: "ask_followup_question",
params: { question: "What is your name?" },
nativeArgs: { question: "What is your name?", follow_up: [] },
params: { questions: ["What is your name?"] },
nativeArgs: { questions: ["What is your name?"], follow_up: [] },
},
]

Expand Down
46 changes: 38 additions & 8 deletions src/core/prompts/tools/native-tools/ask_followup_question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import type OpenAI from "openai"
const ASK_FOLLOWUP_QUESTION_DESCRIPTION = `Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively.

Parameters:
- question: (required) A clear, specific question addressing the information needed
- questions: (required) A list of questions to ask. Each question can be a simple string or an object with "text" and "options" for multiple choice.
- follow_up: (required) A list of 2-4 suggested answers. Suggestions must be complete, actionable answers without placeholders. Optionally include mode to switch modes (code/architect/etc.)

Example: Asking for file path
{ "question": "What is the path to the frontend-config.json file?", "follow_up": [{ "text": "./src/frontend-config.json", "mode": null }, { "text": "./config/frontend-config.json", "mode": null }, { "text": "./frontend-config.json", "mode": null }] }
{ "questions": ["What is the path to the frontend-config.json file?"], "follow_up": [{ "text": "./src/frontend-config.json", "mode": null }, { "text": "./config/frontend-config.json", "mode": null }, { "text": "./frontend-config.json", "mode": null }] }

Example: Asking with multiple questions and choices
{
"questions": [
{ "text": "Which framework are you using?", "options": ["React", "Vue", "Svelte", "Other"] },
"What is your project name?",
{ "text": "Include telemetry?", "options": ["Yes", "No"] }
],
"follow_up": [{ "text": "I've answered the questions", "mode": null }]
}

Example: Asking with mode switch
{ "question": "Would you like me to implement this feature?", "follow_up": [{ "text": "Yes, implement it now", "mode": "code" }, { "text": "No, just plan it out", "mode": "architect" }] }`
{ "questions": ["Would you like me to implement this feature?"], "follow_up": [{ "text": "Yes, implement it now", "mode": "code" }, { "text": "No, just plan it out", "mode": "architect" }] }`

const QUESTION_PARAMETER_DESCRIPTION = `Clear, specific question that captures the missing information you need`
const QUESTIONS_PARAMETER_DESCRIPTION = `List of questions to ask. Each question can be a string or an object with "text" and "options" for multiple choice.`

const FOLLOW_UP_PARAMETER_DESCRIPTION = `Required list of 2-4 suggested responses; each suggestion must be a complete, actionable answer and may include a mode switch`

Expand All @@ -29,9 +39,29 @@ export default {
parameters: {
type: "object",
properties: {
question: {
type: "string",
description: QUESTION_PARAMETER_DESCRIPTION,
questions: {
type: "array",
items: {
anyOf: [
{
type: "string",
},
{
type: "object",
properties: {
text: { type: "string" },
options: {
type: "array",
items: { type: "string" },
},
},
required: ["text", "options"],
additionalProperties: false,
},
],
},
description: QUESTIONS_PARAMETER_DESCRIPTION,
minItems: 1,
},
follow_up: {
type: "array",
Expand All @@ -55,7 +85,7 @@ export default {
maxItems: 4,
},
},
required: ["question", "follow_up"],
required: ["questions", "follow_up"],
additionalProperties: false,
},
},
Expand Down
33 changes: 22 additions & 11 deletions src/core/tools/AskFollowupQuestionTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,40 @@ interface Suggestion {
mode?: string
}

interface Question {
text: string
options?: string[]
}

interface AskFollowupQuestionParams {
question: string
questions: Array<string | Question>
follow_up: Suggestion[]
}

export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> {
readonly name = "ask_followup_question" as const

async execute(params: AskFollowupQuestionParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { question, follow_up } = params
const { questions, follow_up } = params
const { handleError, pushToolResult } = callbacks

try {
if (!question) {
if (!questions || questions.length === 0) {
task.consecutiveMistakeCount++
task.recordToolError("ask_followup_question")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "question"))
pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", "questions"))
return
}

// Transform follow_up suggestions to the format expected by task.ask
const follow_up_json = {
question,
suggest: follow_up.map((s) => ({ answer: s.text, mode: s.mode })),
const followup_json = {
questions,
suggest: follow_up?.map((s) => ({ answer: s.text, mode: s.mode })) || [],
}

task.consecutiveMistakeCount = 0
const { text, images } = await task.ask("followup", JSON.stringify(follow_up_json), false)
const { text, images } = await task.ask("followup", JSON.stringify(followup_json), false)
await task.say("user_feedback", text ?? "", images)
pushToolResult(formatResponse.toolResult(`<user_message>\n${text}\n</user_message>`, images))
} catch (error) {
Expand All @@ -46,11 +51,17 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> {
}

override async handlePartial(task: Task, block: ToolUse<"ask_followup_question">): Promise<void> {
const question: string | undefined = block.nativeArgs?.question ?? block.params.question
// Get question from params or nativeArgs
const questions = block.nativeArgs?.questions ?? []
const firstQuestion = questions[0]
const multiQuestionText = typeof firstQuestion === "string" ? firstQuestion : firstQuestion?.text
const singleQuestionText = (block.nativeArgs as any)?.question ?? block.params.question

const questionText = multiQuestionText ?? singleQuestionText

// During partial streaming, only show the question to avoid displaying raw JSON
// The full JSON with suggestions will be sent when the tool call is complete (!block.partial)
await task.ask("followup", question ?? "", block.partial).catch(() => {})
// The full JSON with suggestions will be sent when the tool call is complete
await task.ask("followup", questionText ?? "", block.partial).catch(() => {})
}
}

Expand Down
Loading
Loading