Merge branch 'RooCodeInc:main' into main

This commit is contained in:
Murilo Pires 2025-07-07 17:03:47 -03:00 committed by GitHub
commit 39c5cf773a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 3611 additions and 97 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Fix model search being prefilled in dropdown to prevent confusion in available models

View file

@ -24,12 +24,16 @@ export const codebaseIndexConfigSchema = z.object({
codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini"]).optional(),
codebaseIndexEmbedderBaseUrl: z.string().optional(),
codebaseIndexEmbedderModelId: z.string().optional(),
codebaseIndexEmbedderModelDimension: z.number().optional(),
codebaseIndexSearchMinScore: z.number().min(0).max(1).optional(),
codebaseIndexSearchMaxResults: z
.number()
.min(CODEBASE_INDEX_DEFAULTS.MIN_SEARCH_RESULTS)
.max(CODEBASE_INDEX_DEFAULTS.MAX_SEARCH_RESULTS)
.optional(),
// OpenAI Compatible specific fields
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
})
export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>

View file

@ -47,6 +47,7 @@ export const globalSettingsSchema = z.object({
alwaysAllowExecute: z.boolean().optional(),
alwaysAllowFollowupQuestions: z.boolean().optional(),
followupAutoApproveTimeoutMs: z.number().optional(),
alwaysAllowUpdateTodoList: z.boolean().optional(),
allowedCommands: z.array(z.string()).optional(),
allowedMaxRequests: z.number().nullish(),
autoCondenseContext: z.boolean().optional(),
@ -195,6 +196,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
alwaysAllowSubtasks: true,
alwaysAllowExecute: true,
alwaysAllowFollowupQuestions: true,
alwaysAllowUpdateTodoList: true,
followupAutoApproveTimeoutMs: 0,
allowedCommands: ["*"],

View file

@ -20,3 +20,4 @@ export * from "./terminal.js"
export * from "./tool.js"
export * from "./type-fu.js"
export * from "./vscode.js"
export * from "./todo.js"

View file

@ -106,6 +106,7 @@ export const clineSays = [
"condense_context",
"condense_context_error",
"codebase_search_result",
"user_edit_todos",
] as const
export const clineSaySchema = z.enum(clineSays)

View file

@ -0,0 +1,19 @@
import { z } from "zod"
/**
* TodoStatus
*/
export const todoStatusSchema = z.enum(["pending", "in_progress", "completed"] as const)
export type TodoStatus = z.infer<typeof todoStatusSchema>
/**
* TodoItem
*/
export const todoItemSchema = z.object({
id: z.string(),
content: z.string(),
status: todoStatusSchema,
})
export type TodoItem = z.infer<typeof todoItemSchema>

View file

@ -33,6 +33,7 @@ export const toolNames = [
"new_task",
"fetch_instructions",
"codebase_search",
"update_todo_list",
] as const
export const toolNamesSchema = z.enum(toolNames)

View file

@ -26,6 +26,7 @@ import { attemptCompletionTool } from "../tools/attemptCompletionTool"
import { newTaskTool } from "../tools/newTaskTool"
import { checkpointSave } from "../checkpoints"
import { updateTodoListTool } from "../tools/updateTodoListTool"
import { formatResponse } from "../prompts/responses"
import { validateToolUse } from "../tools/validateToolUse"
@ -205,6 +206,8 @@ export async function presentAssistantMessage(cline: Task) {
return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]`
case "codebase_search": // Add case for the new tool
return `[${block.name} for '${block.params.query}']`
case "update_todo_list":
return `[${block.name}]`
case "new_task": {
const mode = block.params.mode ?? defaultModeSlug
const message = block.params.message ?? "(no message)"
@ -410,6 +413,9 @@ export async function presentAssistantMessage(cline: Task) {
case "write_to_file":
await writeToFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
break
case "update_todo_list":
await updateTodoListTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
break
case "apply_diff": {
// Get the provider and state to check experiment settings
const provider = cline.providerRef.deref()

File diff suppressed because it is too large Load diff

View file

@ -68,6 +68,9 @@ export async function importSettingsFromPath(
(globalSettings.customModes ?? []).map((mode) => customModesManager.updateCustomMode(mode.slug, mode)),
)
// OpenAI Compatible settings are now correctly stored in codebaseIndexConfig
// They will be imported automatically with the config - no special handling needed
await providerSettingsManager.import(providerProfiles)
await contextProxy.setValues(globalSettings)
@ -161,10 +164,16 @@ export const exportSettings = async ({ providerSettingsManager, contextProxy }:
return
}
// OpenAI Compatible settings are now correctly stored in codebaseIndexConfig
// No workaround needed - they will be exported automatically with the config
const dirname = path.dirname(uri.fsPath)
await fs.mkdir(dirname, { recursive: true })
await safeWriteJson(uri.fsPath, { providerProfiles, globalSettings })
} catch (e) {}
} catch (e) {
console.error("Failed to export settings:", e)
// Don't re-throw - the UI will handle showing error messages
}
}
/**

View file

@ -18,6 +18,7 @@ import { arePathsEqual } from "../../utils/path"
import { formatResponse } from "../prompts/responses"
import { Task } from "../task/Task"
import { formatReminderSection } from "./reminder"
export async function getEnvironmentDetails(cline: Task, includeFileDetails: boolean = false) {
let details = ""
@ -273,5 +274,6 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
}
}
return `<environment_details>\n${details.trim()}\n</environment_details>`
const reminderSection = formatReminderSection(cline.todoList)
return `<environment_details>\n${details.trim()}\n${reminderSection}\n</environment_details>`
}

View file

@ -0,0 +1,38 @@
import { TodoItem, TodoStatus } from "@roo-code/types"
/**
* Format the reminders section as a markdown block in English, with basic instructions.
*/
export function formatReminderSection(todoList?: TodoItem[]): string {
if (!todoList || todoList.length === 0) {
return ""
}
const statusMap: Record<TodoStatus, string> = {
pending: "Pending",
in_progress: "In Progress",
completed: "Completed",
}
const lines: string[] = [
"====",
"",
"REMINDERS",
"",
"Below is your current list of reminders for this task. Keep them updated as you progress.",
"",
]
lines.push("| # | Content | Status |")
lines.push("|---|---------|--------|")
todoList.forEach((item, idx) => {
const escapedContent = item.content.replace(/\\/g, "\\\\").replace(/\|/g, "\\|")
lines.push(`| ${idx + 1} | ${escapedContent} | ${statusMap[item.status] || item.status} |`)
})
lines.push("")
lines.push(
"",
"IMPORTANT: When task status changes, remember to call the `update_todo_list` tool to update your progress.",
"",
)
return lines.join("\n")
}

View file

@ -368,6 +368,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -265,6 +265,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -417,6 +417,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -417,6 +417,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -373,6 +373,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -368,6 +368,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -421,6 +421,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -368,6 +368,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -456,6 +456,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -368,6 +368,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -421,6 +421,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -417,6 +417,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -368,6 +368,76 @@ Example:
</new_task>
## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
# Tool Use Guidelines
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.

View file

@ -1,7 +1,7 @@
import * as vscode from "vscode"
import * as os from "os"
import type { ModeConfig, PromptComponent, CustomModePrompts } from "@roo-code/types"
import type { ModeConfig, PromptComponent, CustomModePrompts, TodoItem } from "@roo-code/types"
import { Mode, modes, defaultModeSlug, getModeBySlug, getGroupName, getModeSelection } from "../../shared/modes"
import { DiffStrategy } from "../../shared/tools"
@ -44,6 +44,7 @@ async function generatePrompt(
rooIgnoreInstructions?: string,
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
todoList?: TodoItem[],
): Promise<string> {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
@ -122,6 +123,7 @@ export const SYSTEM_PROMPT = async (
rooIgnoreInstructions?: string,
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
todoList?: TodoItem[],
): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
@ -195,5 +197,6 @@ ${customInstructions}`
rooIgnoreInstructions,
partialReadsEnabled,
settings,
todoList,
)
}

View file

@ -22,6 +22,7 @@ import { getAccessMcpResourceDescription } from "./access-mcp-resource"
import { getSwitchModeDescription } from "./switch-mode"
import { getNewTaskDescription } from "./new-task"
import { getCodebaseSearchDescription } from "./codebase-search"
import { getUpdateTodoListDescription } from "./update-todo-list"
import { CodeIndexManager } from "../../../services/code-index/manager"
// Map of tool names to their description functions
@ -45,6 +46,7 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
search_and_replace: (args) => getSearchAndReplaceDescription(args),
apply_diff: (args) =>
args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "",
update_todo_list: (args) => getUpdateTodoListDescription(args),
}
export function getToolDescriptionsForMode(

View file

@ -0,0 +1,76 @@
import { ToolArgs } from "./types"
/**
* Get the description for the update_todo_list tool.
*/
export function getUpdateTodoListDescription(args?: ToolArgs): string {
return `## update_todo_list
**Description:**
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
**Checklist Format:**
- Use a single-level markdown checklist (no nesting or subtasks).
- List todos in the intended execution order.
- Status options:
- [ ] Task description (pending)
- [x] Task description (completed)
- [-] Task description (in progress)
**Status Rules:**
- [ ] = pending (not started)
- [x] = completed (fully finished, no unresolved issues)
- [-] = in_progress (currently being worked on)
**Core Principles:**
- Before updating, always confirm which todos have been completed since the last update.
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
- Do not remove any unfinished todos unless explicitly instructed.
- Always retain all unfinished tasks, updating their status as needed.
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
- Remove tasks only if they are no longer relevant or if the user requests deletion.
**Usage Example:**
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[-] Implement core logic
[ ] Write tests
[ ] Update documentation
</todos>
</update_todo_list>
*After completing "Implement core logic" and starting "Write tests":*
<update_todo_list>
<todos>
[x] Analyze requirements
[x] Design architecture
[x] Implement core logic
[-] Write tests
[ ] Update documentation
[ ] Add performance benchmarks
</todos>
</update_todo_list>
**When to Use:**
- The task involves multiple steps or requires ongoing tracking.
- You need to update the status of several todos at once.
- New actionable items are discovered during task execution.
- The user requests a todo list or provides multiple tasks.
- The task is complex and benefits from clear, stepwise progress tracking.
**When NOT to Use:**
- There is only a single, trivial task.
- The task can be completed in one or two simple steps.
- The request is purely conversational or informational.
**Task Management Guidelines:**
- Mark task as completed immediately after all work of the current task is done.
- Start the next task by marking it as in_progress.
- Add new todos as soon as they are identified.
- Use clear, descriptive task names.
`
}

View file

@ -20,6 +20,7 @@ import {
type ToolProgressStatus,
type HistoryItem,
TelemetryEventName,
TodoItem,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService } from "@roo-code/cloud"
@ -85,6 +86,7 @@ import { processUserContentMentions } from "../mentions/processUserContentMentio
import { ApiMessage } from "../task-persistence/apiMessages"
import { getMessagesSinceLastSummary, summarizeConversation } from "../condense"
import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
import { restoreTodoListForTask } from "../tools/updateTodoListTool"
// Constants
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
@ -122,6 +124,7 @@ export type TaskOptions = {
}
export class Task extends EventEmitter<ClineEvents> {
todoList?: TodoItem[]
readonly taskId: string
readonly instanceId: string
@ -370,6 +373,7 @@ export class Task extends EventEmitter<ClineEvents> {
public async overwriteClineMessages(newMessages: ClineMessage[]) {
this.clineMessages = newMessages
restoreTodoListForTask(this)
await this.saveClineMessages()
}
@ -1716,7 +1720,9 @@ export class Task extends EventEmitter<ClineEvents> {
const contextWindow = modelInfo.contextWindow
const currentProfileId = state?.listApiConfigMeta.find((profile) => profile.name === state?.currentApiConfigName)?.id ?? "default";
const currentProfileId =
state?.listApiConfigMeta.find((profile) => profile.name === state?.currentApiConfigName)?.id ??
"default"
const truncateResult = await truncateConversationIfNeeded({
messages: this.apiConversationHistory,

View file

@ -0,0 +1,236 @@
import { Task } from "../task/Task"
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
import { formatResponse } from "../prompts/responses"
import cloneDeep from "clone-deep"
import crypto from "crypto"
import { TodoItem, TodoStatus, todoStatusSchema } from "@roo-code/types"
import { getLatestTodo } from "../../shared/todo"
let approvedTodoList: TodoItem[] | undefined = undefined
/**
* Add a todo item to the task's todoList.
*/
export function addTodoToTask(cline: Task, content: string, status: TodoStatus = "pending", id?: string): TodoItem {
const todo: TodoItem = {
id: id ?? crypto.randomUUID(),
content,
status,
}
if (!cline.todoList) cline.todoList = []
cline.todoList.push(todo)
return todo
}
/**
* Update the status of a todo item by id.
*/
export function updateTodoStatusForTask(cline: Task, id: string, nextStatus: TodoStatus): boolean {
if (!cline.todoList) return false
const idx = cline.todoList.findIndex((t) => t.id === id)
if (idx === -1) return false
const current = cline.todoList[idx]
if (
(current.status === "pending" && nextStatus === "in_progress") ||
(current.status === "in_progress" && nextStatus === "completed") ||
current.status === nextStatus
) {
cline.todoList[idx] = { ...current, status: nextStatus }
return true
}
return false
}
/**
* Remove a todo item by id.
*/
export function removeTodoFromTask(cline: Task, id: string): boolean {
if (!cline.todoList) return false
const idx = cline.todoList.findIndex((t) => t.id === id)
if (idx === -1) return false
cline.todoList.splice(idx, 1)
return true
}
/**
* Get a copy of the todoList.
*/
export function getTodoListForTask(cline: Task): TodoItem[] | undefined {
return cline.todoList?.slice()
}
/**
* Set the todoList for the task.
*/
export async function setTodoListForTask(cline?: Task, todos?: TodoItem[]) {
if (cline === undefined) return
cline.todoList = Array.isArray(todos) ? todos : []
}
/**
* Restore the todoList from argument or from clineMessages.
*/
export function restoreTodoListForTask(cline: Task, todoList?: TodoItem[]) {
if (todoList) {
cline.todoList = Array.isArray(todoList) ? todoList : []
return
}
cline.todoList = getLatestTodo(cline.clineMessages)
}
/**
* Convert TodoItem[] to markdown checklist string.
* @param todos TodoItem array
* @returns markdown checklist string
*/
function todoListToMarkdown(todos: TodoItem[]): string {
return todos
.map((t) => {
let box = "[ ]"
if (t.status === "completed") box = "[x]"
else if (t.status === "in_progress") box = "[-]"
return `${box} ${t.content}`
})
.join("\n")
}
function normalizeStatus(status: string | undefined): TodoStatus {
if (status === "completed") return "completed"
if (status === "in_progress") return "in_progress"
return "pending"
}
function parseMarkdownChecklist(md: string): TodoItem[] {
if (typeof md !== "string") return []
const lines = md
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean)
const todos: TodoItem[] = []
for (const line of lines) {
const match = line.match(/^\[\s*([ xX\-~])\s*\]\s+(.+)$/)
if (!match) continue
let status: TodoStatus = "pending"
if (match[1] === "x" || match[1] === "X") status = "completed"
else if (match[1] === "-" || match[1] === "~") status = "in_progress"
const id = crypto
.createHash("md5")
.update(match[2] + status)
.digest("hex")
todos.push({
id,
content: match[2],
status,
})
}
return todos
}
export function setPendingTodoList(todos: TodoItem[]) {
approvedTodoList = todos
}
function validateTodos(todos: any[]): { valid: boolean; error?: string } {
if (!Array.isArray(todos)) return { valid: false, error: "todos must be an array" }
for (const [i, t] of todos.entries()) {
if (!t || typeof t !== "object") return { valid: false, error: `Item ${i + 1} is not an object` }
if (!t.id || typeof t.id !== "string") return { valid: false, error: `Item ${i + 1} is missing id` }
if (!t.content || typeof t.content !== "string")
return { valid: false, error: `Item ${i + 1} is missing content` }
if (t.status && !todoStatusSchema.options.includes(t.status as TodoStatus))
return { valid: false, error: `Item ${i + 1} has invalid status` }
}
return { valid: true }
}
/**
* Update the todo list for a task.
* @param cline Task instance
* @param block ToolUse block
* @param askApproval AskApproval function
* @param handleError HandleError function
* @param pushToolResult PushToolResult function
* @param removeClosingTag RemoveClosingTag function
* @param userEdited If true, only show "User Edit Succeeded" and do nothing else
*/
export async function updateTodoListTool(
cline: Task,
block: ToolUse,
askApproval: AskApproval,
handleError: HandleError,
pushToolResult: PushToolResult,
removeClosingTag: RemoveClosingTag,
userEdited?: boolean,
) {
// If userEdited is true, only show "User Edit Succeeded" and do nothing else
if (userEdited === true) {
pushToolResult("User Edit Succeeded")
return
}
try {
const todosRaw = block.params.todos
let todos: TodoItem[]
try {
todos = parseMarkdownChecklist(todosRaw || "")
} catch {
cline.consecutiveMistakeCount++
cline.recordToolError("update_todo_list")
pushToolResult(formatResponse.toolError("The todos parameter is not valid markdown checklist or JSON"))
return
}
const { valid, error } = validateTodos(todos)
if (!valid && !block.partial) {
cline.consecutiveMistakeCount++
cline.recordToolError("update_todo_list")
pushToolResult(formatResponse.toolError(error || "todos parameter validation failed"))
return
}
let normalizedTodos: TodoItem[] = todos.map((t) => ({
id: t.id,
content: t.content,
status: normalizeStatus(t.status),
}))
const approvalMsg = JSON.stringify({
tool: "updateTodoList",
todos: normalizedTodos,
})
if (block.partial) {
await cline.ask("tool", approvalMsg, block.partial).catch(() => {})
return
}
approvedTodoList = cloneDeep(normalizedTodos)
const didApprove = await askApproval("tool", approvalMsg)
if (!didApprove) {
pushToolResult("User declined to update the todoList.")
return
}
const isTodoListChanged =
approvedTodoList !== undefined && JSON.stringify(normalizedTodos) !== JSON.stringify(approvedTodoList)
if (isTodoListChanged) {
normalizedTodos = approvedTodoList ?? []
cline.say(
"user_edit_todos",
JSON.stringify({
tool: "updateTodoList",
todos: normalizedTodos,
}),
)
}
await setTodoListForTask(cline, normalizedTodos)
// If todo list changed, output new todo list in markdown format
if (isTodoListChanged) {
const md = todoListToMarkdown(normalizedTodos)
pushToolResult(formatResponse.toolResult("User edits todo:\n\n" + md))
} else {
pushToolResult(formatResponse.toolResult("Todo list updated successfully."))
}
} catch (error) {
await handleError("update todo list", error)
}
}

View file

@ -1347,6 +1347,7 @@ export class ClineProvider
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysAllowUpdateTodoList,
allowedMaxRequests,
autoCondenseContext,
autoCondenseContextPercent,
@ -1433,6 +1434,7 @@ export class ClineProvider
alwaysAllowMcp: alwaysAllowMcp ?? false,
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
alwaysAllowUpdateTodoList: alwaysAllowUpdateTodoList ?? false,
allowedMaxRequests,
autoCondenseContext: autoCondenseContext ?? true,
autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
@ -1601,6 +1603,7 @@ export class ClineProvider
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
alwaysAllowFollowupQuestions: stateValues.alwaysAllowFollowupQuestions ?? false,
alwaysAllowUpdateTodoList: stateValues.alwaysAllowUpdateTodoList ?? false,
followupAutoApproveTimeoutMs: stateValues.followupAutoApproveTimeoutMs ?? 60000,
allowedMaxRequests: stateValues.allowedMaxRequests,
autoCondenseContext: stateValues.autoCondenseContext ?? true,

View file

@ -46,6 +46,7 @@ import { getCommand } from "../../utils/commands"
const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
import { MarketplaceManager, MarketplaceItemType } from "../../services/marketplace"
import { setPendingTodoList } from "../tools/updateTodoListTool"
export const webviewMessageHandler = async (
provider: ClineProvider,
@ -185,6 +186,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("alwaysAllowSubtasks", message.bool)
await provider.postStateToWebview()
break
case "alwaysAllowUpdateTodoList":
await updateGlobalState("alwaysAllowUpdateTodoList", message.bool)
await provider.postStateToWebview()
break
case "askResponse":
provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
break
@ -1317,6 +1322,14 @@ export const webviewMessageHandler = async (
}
break
}
case "updateTodoList": {
const payload = message.payload as { todos?: any[] }
const todos = payload?.todos
if (Array.isArray(todos)) {
await setPendingTodoList(todos)
}
break
}
case "saveApiConfiguration":
if (message.text && message.apiConfiguration) {
try {

View file

@ -96,10 +96,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "text-embedding-3-large",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
}
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") return mockGlobalState
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
@ -134,11 +134,11 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: 1024,
}
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") return mockGlobalState
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
return undefined
})
setupSecretMocks({
@ -173,11 +173,11 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
// modelDimension is not set
}
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") return mockGlobalState
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return undefined
return undefined
})
setupSecretMocks({
@ -197,6 +197,7 @@ describe("CodeIndexConfigManager", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
// modelDimension is undefined when not set
},
qdrantUrl: "http://qdrant.local",
qdrantApiKey: "test-qdrant-key",
@ -211,11 +212,11 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: "invalid-dimension", // Invalid type
}
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") return mockGlobalState
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return "invalid-dimension"
return undefined
})
setupSecretMocks({
@ -461,9 +462,9 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
codebaseIndexOpenAiCompatibleBaseUrl: "https://old-api.example.com/v1",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://old-api.example.com/v1"
return undefined
})
setupSecretMocks({
@ -481,9 +482,9 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
codebaseIndexOpenAiCompatibleBaseUrl: "https://new-api.example.com/v1",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://new-api.example.com/v1"
return undefined
})
@ -500,9 +501,9 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
setupSecretMocks({
@ -531,10 +532,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: 1024,
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
return undefined
})
setupSecretMocks({
@ -552,10 +553,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: 2048,
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 2048
return undefined
})
@ -614,10 +615,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
// modelDimension not set initially
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return undefined
return undefined
})
setupSecretMocks({
@ -635,10 +636,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: 1024,
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
return undefined
})
@ -655,10 +656,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
codebaseIndexOpenAiCompatibleModelDimension: 1024,
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return 1024
return undefined
})
setupSecretMocks({
@ -676,10 +677,10 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "custom-model",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
// modelDimension removed
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
if (key === "codebaseIndexOpenAiCompatibleModelDimension") return undefined
return undefined
})
@ -1082,9 +1083,9 @@ describe("CodeIndexConfigManager", () => {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexOpenAiCompatibleBaseUrl: "https://api.example.com/v1",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
setupSecretMocks({

View file

@ -1,18 +1,31 @@
import { CodeIndexManager } from "../manager"
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
workspaceFolders: [
{
uri: { fsPath: "/test/workspace" },
name: "test",
index: 0,
},
],
},
}))
// Mock only the essential dependencies
vitest.mock("../../../utils/path", () => ({
getWorkspacePath: vitest.fn(() => "/test/workspace"),
vi.mock("../../../utils/path", () => ({
getWorkspacePath: vi.fn(() => "/test/workspace"),
}))
vitest.mock("../state-manager", () => ({
CodeIndexStateManager: vitest.fn().mockImplementation(() => ({
onProgressUpdate: vitest.fn(),
getCurrentStatus: vitest.fn(),
dispose: vitest.fn(),
vi.mock("../state-manager", () => ({
CodeIndexStateManager: vi.fn().mockImplementation(() => ({
onProgressUpdate: vi.fn(),
getCurrentStatus: vi.fn(),
dispose: vi.fn(),
})),
}))
import { CodeIndexManager } from "../manager"
describe("CodeIndexManager - handleSettingsChange regression", () => {
let mockContext: any
let manager: CodeIndexManager
@ -27,7 +40,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
globalState: {} as any,
extensionUri: {} as any,
extensionPath: "/test/extension",
asAbsolutePath: vitest.fn(),
asAbsolutePath: vi.fn(),
storageUri: {} as any,
storagePath: "/test/storage",
globalStorageUri: {} as any,
@ -58,13 +71,13 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
// Mock a minimal config manager that simulates first-time configuration
const mockConfigManager = {
loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: true }),
loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: true }),
}
;(manager as any)._configManager = mockConfigManager
// Mock the feature state to simulate valid configuration that would normally trigger restart
vitest.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
// The key test: this should NOT throw "CodeIndexManager not initialized" error
await expect(manager.handleSettingsChange()).resolves.not.toThrow()
@ -76,10 +89,10 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
it("should work normally when manager is initialized", async () => {
// Mock a complete config manager with all required properties
const mockConfigManager = {
loadConfiguration: vitest.fn().mockResolvedValue({ requiresRestart: true }),
loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: true }),
isFeatureConfigured: true,
isFeatureEnabled: true,
getConfig: vitest.fn().mockReturnValue({
getConfig: vi.fn().mockReturnValue({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
@ -93,7 +106,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
;(manager as any)._configManager = mockConfigManager
// Simulate an initialized manager by setting the required properties
;(manager as any)._orchestrator = { stopWatcher: vitest.fn() }
;(manager as any)._orchestrator = { stopWatcher: vi.fn() }
;(manager as any)._searchService = {}
;(manager as any)._cacheManager = {}
@ -101,12 +114,12 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
expect(manager.isInitialized).toBe(true)
// Mock the methods that would be called during restart
const recreateServicesSpy = vitest.spyOn(manager as any, "_recreateServices").mockImplementation(() => {})
const startIndexingSpy = vitest.spyOn(manager, "startIndexing").mockResolvedValue()
const recreateServicesSpy = vi.spyOn(manager as any, "_recreateServices").mockImplementation(() => {})
const startIndexingSpy = vi.spyOn(manager, "startIndexing").mockResolvedValue()
// Mock the feature state
vitest.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
vitest.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
vi.spyOn(manager, "isFeatureEnabled", "get").mockReturnValue(true)
vi.spyOn(manager, "isFeatureConfigured", "get").mockReturnValue(true)
await manager.handleSettingsChange()

View file

@ -62,11 +62,12 @@ export class CodeIndexConfigManager {
const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? ""
const openAiCompatibleBaseUrl = this.contextProxy?.getGlobalState("codebaseIndexOpenAiCompatibleBaseUrl") ?? ""
// Fix: Read OpenAI Compatible settings from the correct location within codebaseIndexConfig
const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
const openAiCompatibleModelDimension = this.contextProxy?.getGlobalState(
"codebaseIndexOpenAiCompatibleModelDimension",
) as number | undefined
const openAiCompatibleModelDimension = codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension as
| number
| undefined
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
// Update instance variables with configuration

View file

@ -26,12 +26,17 @@ export class CodeIndexManager {
private _cacheManager: CacheManager | undefined
public static getInstance(context: vscode.ExtensionContext): CodeIndexManager | undefined {
const workspacePath = getWorkspacePath() // Assumes single workspace for now
if (!workspacePath) {
// Use first workspace folder consistently
const workspaceFolders = vscode.workspace.workspaceFolders
if (!workspaceFolders || workspaceFolders.length === 0) {
return undefined
}
// Always use the first workspace folder for consistency across all indexing operations.
// This ensures that the same workspace context is used throughout the indexing pipeline,
// preventing path resolution errors in multi-workspace scenarios.
const workspacePath = workspaceFolders[0].uri.fsPath
if (!CodeIndexManager.instances.has(workspacePath)) {
CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context))
}

View file

@ -464,7 +464,7 @@ export class FileWatcher implements IFileWatcher {
}
// Check if file should be ignored
const relativeFilePath = generateRelativeFilePath(filePath)
const relativeFilePath = generateRelativeFilePath(filePath, this.workspacePath)
if (
!this.ignoreController.validateAccess(filePath) ||
(this.ignoreInstance && this.ignoreInstance.ignores(relativeFilePath))
@ -512,7 +512,7 @@ export class FileWatcher implements IFileWatcher {
const { embeddings } = await this.embedder.createEmbeddings(texts)
pointsToUpsert = blocks.map((block, index) => {
const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path)
const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path, this.workspacePath)
const stableName = `${normalizedAbsolutePath}:${block.start_line}`
const pointId = uuidv5(stableName, QDRANT_CODE_BLOCK_NAMESPACE)
@ -520,7 +520,7 @@ export class FileWatcher implements IFileWatcher {
id: pointId,
vector: embeddings[index],
payload: {
filePath: generateRelativeFilePath(normalizedAbsolutePath),
filePath: generateRelativeFilePath(normalizedAbsolutePath, this.workspacePath),
codeChunk: block.content,
startLine: block.start_line,
endLine: block.end_line,

View file

@ -4,6 +4,7 @@ import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController"
import { stat } from "fs/promises"
import * as path from "path"
import { generateNormalizedAbsolutePath, generateRelativeFilePath } from "../shared/get-relative-path"
import { getWorkspacePathForContext } from "../../../utils/path"
import { scannerExtensions } from "../shared/supported-extensions"
import * as vscode from "vscode"
import { CodeBlock, ICodeParser, IEmbedder, IVectorStore, IDirectoryScanner } from "../interfaces"
@ -49,6 +50,9 @@ export class DirectoryScanner implements IDirectoryScanner {
onFileParsed?: (fileBlockCount: number) => void,
): Promise<{ codeBlocks: CodeBlock[]; stats: { processed: number; skipped: number }; totalBlockCount: number }> {
const directoryPath = directory
// Capture workspace context at scan start
const scanWorkspace = getWorkspacePathForContext(directoryPath)
// Get all files recursively (handles .gitignore automatically)
const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT)
@ -66,7 +70,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Filter by supported extensions, ignore patterns, and excluded directories
const supportedPaths = allowedPaths.filter((filePath) => {
const ext = path.extname(filePath).toLowerCase()
const relativeFilePath = generateRelativeFilePath(filePath)
const relativeFilePath = generateRelativeFilePath(filePath, scanWorkspace)
// Check if file is in an ignored directory using the shared helper
if (isPathInIgnoredDirectory(filePath)) {
@ -169,6 +173,7 @@ export class DirectoryScanner implements IDirectoryScanner {
batchBlocks,
batchTexts,
batchFileInfos,
scanWorkspace,
onError,
onBlocksIndexed,
),
@ -185,12 +190,15 @@ export class DirectoryScanner implements IDirectoryScanner {
await this.cacheManager.updateHash(filePath, currentFileHash)
}
} catch (error) {
console.error(`Error processing file ${filePath}:`, error)
console.error(`Error processing file ${filePath} in workspace ${scanWorkspace}:`, error)
if (onError) {
onError(
error instanceof Error
? error
: new Error(t("embeddings:scanner.unknownErrorProcessingFile", { filePath })),
? new Error(`${error.message} (Workspace: ${scanWorkspace}, File: ${filePath})`)
: new Error(
t("embeddings:scanner.unknownErrorProcessingFile", { filePath }) +
` (Workspace: ${scanWorkspace})`,
),
)
}
}
@ -214,7 +222,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Queue final batch processing
const batchPromise = batchLimiter(() =>
this.processBatch(batchBlocks, batchTexts, batchFileInfos, onError, onBlocksIndexed),
this.processBatch(batchBlocks, batchTexts, batchFileInfos, scanWorkspace, onError, onBlocksIndexed),
)
activeBatchPromises.push(batchPromise)
} finally {
@ -235,15 +243,20 @@ export class DirectoryScanner implements IDirectoryScanner {
await this.qdrantClient.deletePointsByFilePath(cachedFilePath)
await this.cacheManager.deleteHash(cachedFilePath)
} catch (error) {
console.error(`[DirectoryScanner] Failed to delete points for ${cachedFilePath}:`, error)
console.error(
`[DirectoryScanner] Failed to delete points for ${cachedFilePath} in workspace ${scanWorkspace}:`,
error,
)
if (onError) {
onError(
error instanceof Error
? error
? new Error(
`${error.message} (Workspace: ${scanWorkspace}, File: ${cachedFilePath})`,
)
: new Error(
t("embeddings:scanner.unknownErrorDeletingPoints", {
filePath: cachedFilePath,
}),
}) + ` (Workspace: ${scanWorkspace})`,
),
)
}
@ -267,6 +280,7 @@ export class DirectoryScanner implements IDirectoryScanner {
batchBlocks: CodeBlock[],
batchTexts: string[],
batchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[],
scanWorkspace: string,
onError?: (error: Error) => void,
onBlocksIndexed?: (indexedCount: number) => void,
): Promise<void> {
@ -292,11 +306,14 @@ export class DirectoryScanner implements IDirectoryScanner {
await this.qdrantClient.deletePointsByMultipleFilePaths(uniqueFilePaths)
} catch (deleteError) {
console.error(
`[DirectoryScanner] Failed to delete points for ${uniqueFilePaths.length} files before upsert:`,
`[DirectoryScanner] Failed to delete points for ${uniqueFilePaths.length} files before upsert in workspace ${scanWorkspace}:`,
deleteError,
)
// Re-throw the error to stop processing this batch attempt
throw deleteError
// Re-throw the error with workspace context
throw new Error(
`Failed to delete points for ${uniqueFilePaths.length} files. Workspace: ${scanWorkspace}. ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`,
{ cause: deleteError },
)
}
}
// --- End Deletion Step ---
@ -306,7 +323,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Prepare points for Qdrant
const points = batchBlocks.map((block, index) => {
const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path)
const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path, scanWorkspace)
// Use segmentHash for unique ID generation to handle multiple segments from same line
const pointId = uuidv5(block.segmentHash, QDRANT_CODE_BLOCK_NAMESPACE)
@ -315,7 +332,7 @@ export class DirectoryScanner implements IDirectoryScanner {
id: pointId,
vector: embeddings[index],
payload: {
filePath: generateRelativeFilePath(normalizedAbsolutePath),
filePath: generateRelativeFilePath(normalizedAbsolutePath, scanWorkspace),
codeChunk: block.content,
startLine: block.start_line,
endLine: block.end_line,
@ -335,7 +352,10 @@ export class DirectoryScanner implements IDirectoryScanner {
success = true
} catch (error) {
lastError = error as Error
console.error(`[DirectoryScanner] Error processing batch (attempt ${attempts}):`, error)
console.error(
`[DirectoryScanner] Error processing batch (attempt ${attempts}) in workspace ${scanWorkspace}:`,
error,
)
if (attempts < MAX_BATCH_RETRIES) {
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempts - 1)

View file

@ -0,0 +1,64 @@
import { describe, it, expect } from "vitest"
import path from "path"
import { generateNormalizedAbsolutePath, generateRelativeFilePath } from "../get-relative-path"
describe("get-relative-path", () => {
describe("generateNormalizedAbsolutePath", () => {
it("should use provided workspace root", () => {
const filePath = "src/file.ts"
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const result = generateNormalizedAbsolutePath(filePath, workspaceRoot)
// On Windows, path.resolve adds the drive letter, so we need to use path.resolve for the expected value
expect(result).toBe(path.resolve(workspaceRoot, filePath))
})
it("should handle absolute paths", () => {
const filePath = path.join(path.sep, "absolute", "path", "file.ts")
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const result = generateNormalizedAbsolutePath(filePath, workspaceRoot)
// When an absolute path is provided, it should be resolved to include drive letter on Windows
expect(result).toBe(path.resolve(filePath))
})
it("should normalize paths with . and .. segments", () => {
const filePath = "./src/../src/file.ts"
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const result = generateNormalizedAbsolutePath(filePath, workspaceRoot)
// Use path.resolve to get the expected normalized absolute path
expect(result).toBe(path.resolve(workspaceRoot, "src", "file.ts"))
})
})
describe("generateRelativeFilePath", () => {
it("should use provided workspace root", () => {
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const absolutePath = path.join(workspaceRoot, "src", "file.ts")
const result = generateRelativeFilePath(absolutePath, workspaceRoot)
expect(result).toBe(path.join("src", "file.ts"))
})
it("should handle paths outside workspace", () => {
const absolutePath = path.join(path.sep, "outside", "workspace", "file.ts")
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const result = generateRelativeFilePath(absolutePath, workspaceRoot)
// The result will have .. segments to navigate outside
expect(result).toContain("..")
})
it("should handle same path as workspace", () => {
const workspaceRoot = path.join(path.sep, "custom", "workspace")
const absolutePath = workspaceRoot
const result = generateRelativeFilePath(absolutePath, workspaceRoot)
expect(result).toBe(".")
})
it("should handle multi-workspace scenarios", () => {
// Simulate the error scenario from the issue
const workspaceRoot = path.join(path.sep, "Users", "test", "project")
const absolutePath = path.join(path.sep, "Users", "test", "admin", ".prettierrc.json")
const result = generateRelativeFilePath(absolutePath, workspaceRoot)
// Should generate a valid relative path, not throw an error
expect(result).toBe(path.join("..", "admin", ".prettierrc.json"))
})
})
})

View file

@ -1,16 +1,14 @@
import path from "path"
import { getWorkspacePath } from "../../../utils/path"
/**
* Generates a normalized absolute path from a given file path and workspace root.
* Handles path resolution and normalization to ensure consistent absolute paths.
*
* @param filePath - The file path to normalize (can be relative or absolute)
* @param workspaceRoot - The root directory of the workspace
* @param workspaceRoot - The root directory of the workspace (required)
* @returns The normalized absolute path
*/
export function generateNormalizedAbsolutePath(filePath: string): string {
const workspaceRoot = getWorkspacePath()
export function generateNormalizedAbsolutePath(filePath: string, workspaceRoot: string): string {
// Resolve the path to make it absolute if it's relative
const resolvedPath = path.resolve(workspaceRoot, filePath)
// Normalize to handle any . or .. segments and duplicate slashes
@ -22,11 +20,10 @@ export function generateNormalizedAbsolutePath(filePath: string): string {
* Ensures consistent relative path generation across different platforms.
*
* @param normalizedAbsolutePath - The normalized absolute path to convert
* @param workspaceRoot - The root directory of the workspace
* @param workspaceRoot - The root directory of the workspace (required)
* @returns The relative path from workspaceRoot to the file
*/
export function generateRelativeFilePath(normalizedAbsolutePath: string): string {
const workspaceRoot = getWorkspacePath()
export function generateRelativeFilePath(normalizedAbsolutePath: string, workspaceRoot: string): string {
// Generate the relative path
const relativePath = path.relative(workspaceRoot, normalizedAbsolutePath)
// Normalize to ensure consistent path separators

View file

@ -181,6 +181,7 @@ export type ExtensionState = Pick<
| "alwaysAllowModeSwitch"
| "alwaysAllowSubtasks"
| "alwaysAllowExecute"
| "alwaysAllowUpdateTodoList"
| "allowedCommands"
| "allowedMaxRequests"
| "browserToolEnabled"

View file

@ -18,8 +18,13 @@ export type PromptMode = Mode | "enhance"
export type AudioType = "notification" | "celebration" | "progress_loop"
export interface UpdateTodoListPayload {
todos: any[]
}
export interface WebviewMessage {
type:
| "updateTodoList"
| "deleteMultipleTasksWithIds"
| "currentApiConfigName"
| "saveApiConfiguration"
@ -38,6 +43,7 @@ export interface WebviewMessage {
| "alwaysAllowWriteProtected"
| "alwaysAllowExecute"
| "alwaysAllowFollowupQuestions"
| "alwaysAllowUpdateTodoList"
| "followupAutoApproveTimeoutMs"
| "webviewDidLaunch"
| "newTask"
@ -73,6 +79,7 @@ export interface WebviewMessage {
| "alwaysAllowModeSwitch"
| "allowedMaxRequests"
| "alwaysAllowSubtasks"
| "alwaysAllowUpdateTodoList"
| "autoCondenseContext"
| "autoCondenseContextPercent"
| "condensingApiConfigId"
@ -287,3 +294,4 @@ export type WebViewMessagePayload =
| IndexingStatusPayload
| IndexClearedPayload
| InstallMarketplaceItemWithParametersPayload
| UpdateTodoListPayload

23
src/shared/todo.ts Normal file
View file

@ -0,0 +1,23 @@
import { ClineMessage } from "@roo-code/types"
export function getLatestTodo(clineMessages: ClineMessage[]) {
const todos = clineMessages
.filter(
(msg) =>
(msg.type === "ask" && msg.ask === "tool") || (msg.type === "say" && msg.say === "user_edit_todos"),
)
.map((msg) => {
try {
return JSON.parse(msg.text ?? "{}")
} catch {
return null
}
})
.filter((item) => item && item.tool === "updateTodoList" && Array.isArray(item.todos))
.map((item) => item.todos)
.pop()
if (todos) {
return todos
} else {
return []
}
}

View file

@ -64,6 +64,7 @@ export const toolParamNames = [
"end_line",
"query",
"args",
"todos",
] as const
export type ToolParamName = (typeof toolParamNames)[number]
@ -188,6 +189,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
insert_content: "insert content",
search_and_replace: "search and replace",
codebase_search: "codebase search",
update_todo_list: "update todo list",
} as const
// Define available tool groups.
@ -226,6 +228,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [
"attempt_completion",
"switch_mode",
"new_task",
"update_todo_list",
] as const
export type DiffResult =

View file

@ -115,3 +115,18 @@ export const getWorkspacePath = (defaultCwdPath = "") => {
}
return cwdPath
}
export const getWorkspacePathForContext = (contextPath?: string): string => {
// If context path provided, find its workspace
if (contextPath) {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(contextPath))
if (workspaceFolder) {
return workspaceFolder.uri.fsPath
}
// Debug logging when falling back
console.debug(`[CodeIndex] No workspace found for context path: ${contextPath}, falling back to default`)
}
// Fall back to current behavior
return getWorkspacePath()
}

View file

@ -26,6 +26,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
allowedMaxRequests,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
@ -36,6 +37,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowSubtasks,
setAlwaysApproveResubmit,
setAlwaysAllowFollowupQuestions,
setAlwaysAllowUpdateTodoList,
setAllowedMaxRequests,
} = useExtensionState()
@ -73,6 +75,9 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
case "alwaysAllowFollowupQuestions":
setAlwaysAllowFollowupQuestions(value)
break
case "alwaysAllowUpdateTodoList":
setAlwaysAllowUpdateTodoList(value)
break
}
},
[
@ -85,6 +90,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowSubtasks,
setAlwaysApproveResubmit,
setAlwaysAllowFollowupQuestions,
setAlwaysAllowUpdateTodoList,
],
)
@ -101,6 +107,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
alwaysAllowSubtasks: alwaysAllowSubtasks,
alwaysApproveResubmit: alwaysApproveResubmit,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList: alwaysAllowUpdateTodoList,
}),
[
alwaysAllowReadOnly,
@ -112,6 +119,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
],
)

View file

@ -21,6 +21,7 @@ import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import { Button } from "@src/components/ui"
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import CodeAccordian from "../common/CodeAccordian"
import CodeBlock from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
@ -52,6 +53,7 @@ interface ChatRowProps {
onSuggestionClick?: (suggestion: SuggestionItem, event?: React.MouseEvent) => void
onBatchFileResponse?: (response: { [key: string]: boolean }) => void
onFollowUpUnmount?: () => void
editable?: boolean
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@ -102,6 +104,7 @@ export const ChatRowContent = ({
onSuggestionClick,
onFollowUpUnmount,
onBatchFileResponse,
editable,
}: ChatRowContentProps) => {
const { t } = useTranslation()
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
@ -431,6 +434,21 @@ export const ChatRowContent = ({
</div>
)
}
case "updateTodoList" as any: {
const todos = (tool as any).todos || []
return (
<UpdateTodoListToolBlock
todos={todos}
content={(tool as any).content}
onChange={(updatedTodos) => {
if (typeof vscode !== "undefined" && vscode?.postMessage) {
vscode.postMessage({ type: "updateTodoList", payload: { todos: updatedTodos } })
}
}}
editable={editable && isLast}
/>
)
}
case "newFileCreated":
return (
<>
@ -1089,6 +1107,8 @@ export const ChatRowContent = ({
const { query = "", results = [] } = parsed?.content || {}
return <CodebaseSearchResultsDisplay query={query} results={results} />
case "user_edit_todos":
return <UpdateTodoListToolBlock userEdited onChange={() => {}} />
default:
return (
<>

View file

@ -46,6 +46,7 @@ import AutoApproveMenu from "./AutoApproveMenu"
import SystemPromptWarning from "./SystemPromptWarning"
import ProfileViolationWarning from "./ProfileViolationWarning"
import { CheckpointWarning } from "./CheckpointWarning"
import { getLatestTodo } from "@roo/todo"
export interface ChatViewProps {
isHidden: boolean
@ -96,6 +97,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
customModes,
telemetrySetting,
hasSystemPromptOverride,
@ -128,6 +130,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// Cline.abort).
const task = useMemo(() => messages.at(0), [messages])
const latestTodos = useMemo(() => {
return getLatestTodo(messages)
}, [messages])
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
// Has to be after api_req_finished are all reduced into api_req_started messages.
@ -907,6 +913,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
return false
}
if (tool?.tool === "updateTodoList") {
return alwaysAllowUpdateTodoList
}
if (tool?.tool === "fetchInstructions") {
if (tool.content === "create_mode") {
return alwaysAllowModeSwitch
@ -960,6 +970,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
alwaysAllowModeSwitch,
alwaysAllowFollowupQuestions,
alwaysAllowSubtasks,
alwaysAllowUpdateTodoList,
],
)
@ -1280,6 +1291,24 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
onBatchFileResponse={handleBatchFileResponse}
onFollowUpUnmount={handleFollowUpUnmount}
editable={
messageOrGroup.type === "ask" &&
messageOrGroup.ask === "tool" &&
(() => {
let tool: any = {}
try {
tool = JSON.parse(messageOrGroup.text || "{}")
} catch (_) {
if (messageOrGroup.text?.includes("updateTodoList")) {
tool = { tool: "updateTodoList" }
}
}
if (tool.tool === "updateTodoList" && alwaysAllowUpdateTodoList) {
return false
}
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
})()
}
/>
)
},
@ -1293,6 +1322,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
handleSuggestionClickInRow,
handleBatchFileResponse,
handleFollowUpUnmount,
alwaysAllowUpdateTodoList,
enableButtons,
primaryButtonText,
],
)
@ -1453,6 +1485,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
buttonsDisabled={sendingDisabled}
handleCondenseContext={handleCondenseContext}
onClose={handleTaskCloseButtonClick}
todos={latestTodos}
/>
{hasSystemPromptOverride && (

View file

@ -121,9 +121,10 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
codebaseIndexConfig.codebaseIndexSearchMinScore ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE,
codeIndexOpenAiKey: "",
codeIndexQdrantApiKey: "",
codebaseIndexOpenAiCompatibleBaseUrl: "",
codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "",
codebaseIndexOpenAiCompatibleApiKey: "",
codebaseIndexOpenAiCompatibleModelDimension: undefined,
codebaseIndexOpenAiCompatibleModelDimension:
codebaseIndexConfig.codebaseIndexOpenAiCompatibleModelDimension || undefined,
codebaseIndexGeminiApiKey: "",
}
setInitialSettings(settings)

View file

@ -20,6 +20,7 @@ import { TaskActions } from "./TaskActions"
import { ShareButton } from "./ShareButton"
import { ContextWindowProgress } from "./ContextWindowProgress"
import { Mention } from "./Mention"
import { TodoListDisplay } from "./TodoListDisplay"
export interface TaskHeaderProps {
task: ClineMessage
@ -32,6 +33,7 @@ export interface TaskHeaderProps {
buttonsDisabled: boolean
handleCondenseContext: (taskId: string) => void
onClose: () => void
todos?: any[]
}
const TaskHeader = ({
@ -45,6 +47,7 @@ const TaskHeader = ({
buttonsDisabled,
handleCondenseContext,
onClose,
todos,
}: TaskHeaderProps) => {
const { t } = useTranslation()
const { apiConfiguration, currentTaskItem } = useExtensionState()
@ -68,11 +71,14 @@ const TaskHeader = ({
</StandardTooltip>
)
const hasTodos = todos && Array.isArray(todos) && todos.length > 0
return (
<div className="py-2 px-3">
<div
className={cn(
"rounded-xs p-2.5 flex flex-col gap-1.5 relative z-1 border",
"p-2.5 flex flex-col gap-1.5 relative z-1 border",
hasTodos ? "rounded-t-xs border-b-0" : "rounded-xs",
isTaskExpanded
? "border-vscode-panel-border text-vscode-foreground"
: "border-vscode-panel-border/80 text-vscode-foreground/80",
@ -214,6 +220,7 @@ const TaskHeader = ({
</>
)}
</div>
<TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />
</div>
)
}

View file

@ -0,0 +1,354 @@
import { useState, useRef, useMemo, useEffect } from "react"
export function TodoListDisplay({ todos }: { todos: any[] }) {
const [isCollapsed, setIsCollapsed] = useState(true)
const ulRef = useRef<HTMLUListElement>(null)
const itemRefs = useRef<(HTMLLIElement | null)[]>([])
const scrollIndex = useMemo(() => {
const inProgressIdx = todos.findIndex((todo: any) => todo.status === "in_progress")
if (inProgressIdx !== -1) return inProgressIdx
return todos.findIndex((todo: any) => todo.status !== "completed")
}, [todos])
// Find the most important todo to display when collapsed
const mostImportantTodo = useMemo(() => {
const inProgress = todos.find((todo: any) => todo.status === "in_progress")
if (inProgress) return inProgress
return todos.find((todo: any) => todo.status !== "completed")
}, [todos])
useEffect(() => {
if (isCollapsed) return
if (!ulRef.current) return
if (scrollIndex === -1) return
const target = itemRefs.current[scrollIndex]
if (target && ulRef.current) {
const ul = ulRef.current
const targetTop = target.offsetTop - ul.offsetTop
const targetHeight = target.offsetHeight
const ulHeight = ul.clientHeight
const scrollTo = targetTop - (ulHeight / 2 - targetHeight / 2)
ul.scrollTop = scrollTo
}
}, [todos, isCollapsed, scrollIndex])
if (!Array.isArray(todos) || todos.length === 0) return null
const totalCount = todos.length
const completedCount = todos.filter((todo: any) => todo.status === "completed").length
const allCompleted = completedCount === totalCount && totalCount > 0
// Create the status icon for the most important todo
const getMostImportantTodoIcon = () => {
if (allCompleted) {
return (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-green)",
marginRight: 8,
marginLeft: 2,
flexShrink: 0,
}}
/>
)
}
if (!mostImportantTodo) {
return (
<span
className="codicon codicon-checklist"
style={{
color: "var(--vscode-foreground)",
marginRight: 8,
marginLeft: 2,
flexShrink: 0,
fontSize: 14,
}}
/>
)
}
if (mostImportantTodo.status === "completed") {
return (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-green)",
marginRight: 8,
marginLeft: 2,
flexShrink: 0,
}}
/>
)
}
if (mostImportantTodo.status === "in_progress") {
return (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-yellow)",
marginRight: 8,
marginLeft: 2,
flexShrink: 0,
}}
/>
)
}
// Default not-started todo
return (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
border: "1px solid var(--vscode-descriptionForeground)",
background: "transparent",
marginRight: 8,
marginLeft: 2,
flexShrink: 0,
}}
/>
)
}
return (
<div
className="border border-t-0 rounded-b-xs relative"
style={{
margin: "0",
padding: "6px 10px",
background: "var(--vscode-editor-background,transparent)",
borderColor: "var(--vscode-panel-border)",
}}>
<div
style={{
display: "flex",
alignItems: "center",
gap: 2,
marginBottom: 0,
cursor: "pointer",
userSelect: "none",
}}
onClick={() => setIsCollapsed((v) => !v)}>
{getMostImportantTodoIcon()}
<span
style={{
fontWeight: 500,
color: allCompleted
? "var(--vscode-charts-green)"
: mostImportantTodo?.status === "in_progress"
? "var(--vscode-charts-yellow)"
: "var(--vscode-foreground)",
flex: 1,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}>
{allCompleted ? "All tasks completed!" : mostImportantTodo?.content || "No pending tasks"}
</span>
<div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
<span
className="codicon codicon-checklist"
style={{
color: "var(--vscode-descriptionForeground)",
fontSize: 12,
}}
/>
<span
style={{
color: "var(--vscode-descriptionForeground)",
fontSize: 12,
fontWeight: 500,
}}>
{completedCount}/{totalCount}
</span>
</div>
</div>
{/* Floating panel for expanded state */}
{!isCollapsed && (
<>
{/* Backdrop */}
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: "rgba(0, 0, 0, 0.1)",
zIndex: 1000,
}}
onClick={() => setIsCollapsed(true)}
/>
{/* Floating panel */}
<div
style={{
position: "absolute",
top: "100%",
left: 0,
right: 0,
marginTop: 4,
background: "var(--vscode-editor-background)",
border: "1px solid var(--vscode-panel-border)",
borderRadius: 6,
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
zIndex: 1001,
maxHeight: "400px",
minHeight: "200px",
overflow: "hidden",
}}>
{/* Panel header */}
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "12px 16px",
borderBottom: "1px solid var(--vscode-panel-border)",
background: "var(--vscode-editor-background)",
}}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span
className="codicon codicon-checklist"
style={{ color: "var(--vscode-foreground)" }}
/>
<span style={{ fontWeight: "bold", fontSize: 14 }}>Todo List</span>
<span
style={{
color: "var(--vscode-descriptionForeground)",
fontSize: 13,
fontWeight: 500,
}}>
{completedCount}/{totalCount}
</span>
</div>
<span
className="codicon codicon-chevron-up"
style={{
fontSize: 14,
opacity: 0.7,
cursor: "pointer",
padding: "4px",
borderRadius: "2px",
}}
onClick={(e) => {
e.stopPropagation()
setIsCollapsed(true)
}}
onMouseEnter={(e) => {
e.currentTarget.style.opacity = "1"
e.currentTarget.style.background = "var(--vscode-toolbar-hoverBackground)"
}}
onMouseLeave={(e) => {
e.currentTarget.style.opacity = "0.7"
e.currentTarget.style.background = "transparent"
}}
/>
</div>
{/* Todo list */}
<ul
ref={ulRef}
style={{
margin: 0,
paddingLeft: 0,
listStyle: "none",
maxHeight: "340px",
overflowY: "auto",
padding: "12px 16px",
}}>
{todos.map((todo: any, idx: number) => {
let icon
if (todo.status === "completed") {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-green)",
marginRight: 8,
marginTop: 7,
flexShrink: 0,
}}
/>
)
} else if (todo.status === "in_progress") {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-yellow)",
marginRight: 8,
marginTop: 7,
flexShrink: 0,
}}
/>
)
} else {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
border: "1px solid var(--vscode-descriptionForeground)",
background: "transparent",
marginRight: 8,
marginTop: 7,
flexShrink: 0,
}}
/>
)
}
return (
<li
key={todo.id || todo.content}
ref={(el) => (itemRefs.current[idx] = el)}
style={{
marginBottom: 8,
display: "flex",
alignItems: "flex-start",
minHeight: 20,
lineHeight: "1.4",
}}>
{icon}
<span
style={{
fontWeight: 500,
color:
todo.status === "completed"
? "var(--vscode-charts-green)"
: todo.status === "in_progress"
? "var(--vscode-charts-yellow)"
: "var(--vscode-foreground)",
wordBreak: "break-word",
}}>
{todo.content}
</span>
</li>
)
})}
</ul>
</div>
</>
)}
</div>
)
}

View file

@ -0,0 +1,498 @@
import React, { useState, useEffect, useRef } from "react"
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import MarkdownBlock from "../common/MarkdownBlock"
interface TodoItem {
id?: string
content: string
status?: "completed" | "in_progress" | string
}
/**
* @description
* Editable Todo List component. Each time the todo list changes (edit, add, delete, status switch), the parent component will be notified via the onChange callback.
* The parent component should synchronize the latest todos to the model in onChange.
*/
interface UpdateTodoListToolBlockProps {
todos?: TodoItem[]
content?: string
/**
* Callback when todos change, be sure to implement and notify the model with the latest todos
* @param todos Latest todo list
*/
onChange: (todos: TodoItem[]) => void
/** Whether editing is allowed (controlled externally) */
editable?: boolean
userEdited?: boolean
}
const STATUS_OPTIONS = [
{ value: "", label: "Not Started", color: "var(--vscode-foreground)", border: "#bbb", bg: "transparent" },
{
value: "in_progress",
label: "In Progress",
color: "var(--vscode-charts-yellow)",
border: "var(--vscode-charts-yellow)",
bg: "rgba(255, 221, 51, 0.15)",
},
{
value: "completed",
label: "Completed",
color: "var(--vscode-charts-green)",
border: "var(--vscode-charts-green)",
bg: "var(--vscode-charts-green)",
},
]
const genId = () => Math.random().toString(36).slice(2, 10)
const UpdateTodoListToolBlock: React.FC<UpdateTodoListToolBlockProps> = ({
todos = [],
content,
onChange,
editable = true,
userEdited = false,
}) => {
const [editTodos, setEditTodos] = useState<TodoItem[]>(
todos.length > 0 ? todos.map((todo) => ({ ...todo, id: todo.id || genId() })) : [],
)
const [adding, setAdding] = useState(false)
const [newContent, setNewContent] = useState("")
const newInputRef = useRef<HTMLInputElement>(null)
const [deleteId, setDeleteId] = useState<string | null>(null)
const [isEditing, setIsEditing] = useState(false)
// Automatically exit edit mode when external editable becomes false
useEffect(() => {
if (!editable && isEditing) {
setIsEditing(false)
}
}, [editable, isEditing])
// Check if onChange is passed
useEffect(() => {
if (typeof onChange !== "function") {
console.warn(
"UpdateTodoListToolBlock: onChange callback not passed, cannot notify model after todo changes!",
)
}
// Only check once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Sync when external props.todos changes
useEffect(() => {
setEditTodos(todos.length > 0 ? todos.map((todo) => ({ ...todo, id: todo.id || genId() })) : [])
}, [todos])
// Auto focus on new item
useEffect(() => {
if (adding && newInputRef.current) {
newInputRef.current.focus()
}
}, [adding])
// Edit content
const handleContentChange = (id: string, value: string) => {
const newTodos = editTodos.map((todo) => (todo.id === id ? { ...todo, content: value } : todo))
setEditTodos(newTodos)
onChange?.(newTodos)
}
// Change status
const handleStatusChange = (id: string, status: string) => {
const newTodos = editTodos.map((todo) => (todo.id === id ? { ...todo, status } : todo))
setEditTodos(newTodos)
onChange?.(newTodos)
}
// Delete (confirmation dialog)
const handleDelete = (id: string) => {
setDeleteId(id)
}
const confirmDelete = () => {
if (!deleteId) return
const newTodos = editTodos.filter((todo) => todo.id !== deleteId)
setEditTodos(newTodos)
onChange?.(newTodos)
setDeleteId(null)
}
const cancelDelete = () => setDeleteId(null)
// Add
const handleAdd = () => {
if (!newContent.trim()) return
const newTodo: TodoItem = {
id: genId(),
content: newContent.trim(),
status: "",
}
const newTodos = [...editTodos, newTodo]
setEditTodos(newTodos)
onChange?.(newTodos)
setNewContent("")
setAdding(false)
}
// Add on Enter
const handleNewInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleAdd()
} else if (e.key === "Escape") {
setAdding(false)
setNewContent("")
}
}
if (userEdited) {
return (
<ToolUseBlock>
<ToolUseBlockHeader>
<div className="flex items-center w-full" style={{ width: "100%" }}>
<span
className="codicon codicon-feedback mr-1.5"
style={{ color: "var(--vscode-charts-yellow)" }}
/>
<span className="font-bold mr-2" style={{ fontWeight: "bold" }}>
User Edit
</span>
<div className="flex-grow" />
</div>
</ToolUseBlockHeader>
<div className="overflow-x-auto max-w-full" style={{ padding: "12px 0 8px 0" }}>
<span className="text-vscode-descriptionForeground">User Edits</span>
</div>
</ToolUseBlock>
)
}
return (
<>
<ToolUseBlock>
<ToolUseBlockHeader>
<div className="flex items-center w-full" style={{ width: "100%" }}>
<span
className="codicon codicon-checklist mr-1.5"
style={{ color: "var(--vscode-foreground)" }}
/>
<span className="font-bold mr-2" style={{ fontWeight: "bold" }}>
Todo List Updated
</span>
<div className="flex-grow" />
{editable && (
<button
onClick={() => setIsEditing(!isEditing)}
style={{
border: isEditing
? "1px solid var(--vscode-button-border)"
: "1px solid var(--vscode-button-secondaryBorder)",
background: isEditing
? "var(--vscode-button-background)"
: "var(--vscode-button-secondaryBackground)",
color: isEditing
? "var(--vscode-button-foreground)"
: "var(--vscode-button-secondaryForeground)",
borderRadius: 4,
padding: "2px 8px",
cursor: "pointer",
fontSize: 13,
marginLeft: 8,
}}>
{isEditing ? "Done" : "Edit"}
</button>
)}
</div>
</ToolUseBlockHeader>
<div className="overflow-x-auto max-w-full" style={{ padding: "6px 0 2px 0" }}>
{Array.isArray(editTodos) && editTodos.length > 0 ? (
<ul style={{ margin: 0, paddingLeft: 0, listStyle: "none" }}>
{editTodos.map((todo, idx) => {
let icon
if (todo.status === "completed") {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-green)",
marginRight: 6,
marginTop: 7,
flexShrink: 0,
}}
/>
)
} else if (todo.status === "in_progress") {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
background: "var(--vscode-charts-yellow)",
marginRight: 6,
marginTop: 7,
flexShrink: 0,
}}
/>
)
} else {
icon = (
<span
style={{
display: "inline-block",
width: 8,
height: 8,
borderRadius: "50%",
border: "1px solid var(--vscode-descriptionForeground)",
background: "transparent",
marginRight: 6,
marginTop: 7,
flexShrink: 0,
}}
/>
)
}
return (
<li
key={todo.id || idx}
style={{
marginBottom: 2,
display: "flex",
alignItems: "flex-start",
minHeight: 20,
}}>
{icon}
{isEditing ? (
<input
type="text"
value={todo.content}
placeholder="Enter todo item"
onChange={(e) => handleContentChange(todo.id!, e.target.value)}
style={{
flex: 1,
minWidth: 0,
fontWeight: 500,
color: "var(--vscode-input-foreground)",
background: "var(--vscode-input-background)",
border: "none",
outline: "none",
fontSize: 13,
marginRight: 6,
padding: "1px 3px",
borderBottom: "1px solid var(--vscode-input-border)",
}}
onBlur={(e) => {
if (!e.target.value.trim()) {
handleDelete(todo.id!)
}
}}
/>
) : (
<span
style={{
flex: 1,
minWidth: 0,
fontWeight: 500,
color:
todo.status === "completed"
? "var(--vscode-charts-green)"
: todo.status === "in_progress"
? "var(--vscode-charts-yellow)"
: "var(--vscode-foreground)",
fontSize: 13,
marginRight: 6,
padding: "1px 3px",
lineHeight: "1.4",
}}>
{todo.content}
</span>
)}
{isEditing && (
<select
value={todo.status || ""}
onChange={(e) => handleStatusChange(todo.id!, e.target.value)}
style={{
marginRight: 6,
borderRadius: 4,
border: "1px solid var(--vscode-input-border)",
background: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
fontSize: 12,
padding: "1px 4px",
}}>
{STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
)}
{isEditing && (
<button
onClick={() => handleDelete(todo.id!)}
style={{
border: "none",
background: "transparent",
color: "#f14c4c",
cursor: "pointer",
fontSize: 14,
marginLeft: 2,
padding: 0,
lineHeight: 1,
}}
title="Remove">
×
</button>
)}
</li>
)
})}
{adding ? (
<li style={{ marginTop: 2, display: "flex", alignItems: "center" }}>
<span style={{ width: 14, marginRight: 6 }} />
<input
ref={newInputRef}
type="text"
value={newContent}
placeholder="Enter todo item, press Enter to add"
onChange={(e) => setNewContent(e.target.value)}
onKeyDown={handleNewInputKeyDown}
style={{
flex: 1,
minWidth: 0,
fontWeight: 500,
color: "var(--vscode-foreground)",
background: "transparent",
border: "none",
outline: "none",
fontSize: 13,
marginRight: 6,
padding: "1px 3px",
borderBottom: "1px solid #eee",
}}
/>
<button
onClick={handleAdd}
disabled={!newContent.trim()}
style={{
border: "1px solid var(--vscode-button-border)",
background: "var(--vscode-button-background)",
color: "var(--vscode-button-foreground)",
borderRadius: 4,
padding: "1px 7px",
cursor: newContent.trim() ? "pointer" : "not-allowed",
fontSize: 12,
marginRight: 4,
}}>
Add
</button>
<button
onClick={() => {
setAdding(false)
setNewContent("")
}}
style={{
border: "1px solid var(--vscode-button-secondaryBorder)",
background: "var(--vscode-button-secondaryBackground)",
color: "var(--vscode-button-secondaryForeground)",
borderRadius: 4,
padding: "1px 7px",
cursor: "pointer",
fontSize: 12,
}}>
Cancel
</button>
</li>
) : (
<li style={{ marginTop: 2 }}>
{isEditing && (
<button
onClick={() => setAdding(true)}
style={{
border: "1px dashed var(--vscode-button-secondaryBorder)",
background: "var(--vscode-button-secondaryBackground)",
color: "var(--vscode-button-secondaryForeground)",
borderRadius: 4,
padding: "1px 8px",
cursor: "pointer",
fontSize: 12,
}}>
+ Add Todo
</button>
)}
</li>
)}
</ul>
) : (
<MarkdownBlock markdown={content} />
)}
</div>
{/* Delete confirmation dialog */}
{deleteId && (
<div
style={{
position: "fixed",
left: 0,
top: 0,
right: 0,
bottom: 0,
background: "rgba(0,0,0,0.15)",
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={cancelDelete}>
<div
style={{
background: "#fff",
borderRadius: 8,
boxShadow: "0 2px 16px rgba(0,0,0,0.15)",
padding: "16px 20px",
minWidth: 200,
zIndex: 10000,
}}
onClick={(e) => e.stopPropagation()}>
<div style={{ marginBottom: 12, fontSize: 14, color: "#333" }}>
Are you sure you want to delete this todo item?
</div>
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
<button
onClick={cancelDelete}
style={{
border: "1px solid #bbb",
background: "transparent",
color: "#888",
borderRadius: 4,
padding: "2px 10px",
cursor: "pointer",
fontSize: 12,
}}>
Cancel
</button>
<button
onClick={confirmDelete}
style={{
border: "1px solid #f14c4c",
background: "#f14c4c",
color: "#fff",
borderRadius: 4,
padding: "2px 10px",
cursor: "pointer",
fontSize: 12,
}}>
Delete
</button>
</div>
</div>
</div>
)}
</ToolUseBlock>
</>
)
}
export default UpdateTodoListToolBlock

View file

@ -45,6 +45,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
| "alwaysAllowFollowupQuestions"
| "followupAutoApproveTimeoutMs"
| "allowedCommands"
| "alwaysAllowUpdateTodoList"
>
}

View file

@ -15,6 +15,7 @@ type AutoApproveToggles = Pick<
| "alwaysAllowSubtasks"
| "alwaysAllowExecute"
| "alwaysAllowFollowupQuestions"
| "alwaysAllowUpdateTodoList"
>
export type AutoApproveSetting = keyof AutoApproveToggles
@ -91,6 +92,13 @@ export const autoApproveSettingsConfig: Record<AutoApproveSetting, AutoApproveCo
icon: "question",
testId: "always-allow-followup-questions-toggle",
},
alwaysAllowUpdateTodoList: {
key: "alwaysAllowUpdateTodoList",
labelKey: "settings:autoApprove.updateTodoList.label",
descriptionKey: "settings:autoApprove.updateTodoList.description",
icon: "checklist",
testId: "always-allow-update-todo-list-toggle",
},
}
type AutoApproveToggleProps = AutoApproveToggles & {

View file

@ -70,7 +70,7 @@ export const ModelPicker = ({
const { id: selectedModelId, info: selectedModelInfo } = useSelectedModel(apiConfiguration)
const [searchValue, setSearchValue] = useState(selectedModelId || "")
const [searchValue, setSearchValue] = useState("")
const onSelect = useCallback(
(modelId: string) => {
@ -87,28 +87,25 @@ export const ModelPicker = ({
}
// Delay to ensure the popover is closed before setting the search value.
selectTimeoutRef.current = setTimeout(() => setSearchValue(modelId), 100)
selectTimeoutRef.current = setTimeout(() => setSearchValue(""), 100)
},
[modelIdKey, setApiConfigurationField],
)
const onOpenChange = useCallback(
(open: boolean) => {
setOpen(open)
const onOpenChange = useCallback((open: boolean) => {
setOpen(open)
// Abandon the current search if the popover is closed.
if (!open) {
// Clear any existing timeout
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
}
// Delay to ensure the popover is closed before setting the search value.
closeTimeoutRef.current = setTimeout(() => setSearchValue(selectedModelId), 100)
// Abandon the current search if the popover is closed.
if (!open) {
// Clear any existing timeout
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
}
},
[selectedModelId],
)
// Clear the search value when closing instead of prefilling it
closeTimeoutRef.current = setTimeout(() => setSearchValue(""), 100)
}
}, [])
const onClearSearch = useCallback(() => {
setSearchValue("")

View file

@ -26,6 +26,7 @@ describe("AutoApproveToggle", () => {
alwaysAllowSubtasks: false,
alwaysAllowExecute: true,
alwaysAllowFollowupQuestions: false,
alwaysAllowUpdateTodoList: true,
onToggle: mockOnToggle,
}

View file

@ -129,6 +129,8 @@ export interface ExtensionStateContextType extends ExtensionState {
autoCondenseContextPercent: number
setAutoCondenseContextPercent: (value: number) => void
routerModels?: RouterModels
alwaysAllowUpdateTodoList?: boolean
setAlwaysAllowUpdateTodoList: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -221,6 +223,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
codebaseIndexSearchMinScore: undefined,
},
codebaseIndexModels: { ollama: {}, openai: {} },
alwaysAllowUpdateTodoList: true,
})
const [didHydrateState, setDidHydrateState] = useState(false)
@ -459,6 +462,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setCustomCondensingPrompt: (value) =>
setState((prevState) => ({ ...prevState, customCondensingPrompt: value })),
setProfileThresholds: (value) => setState((prevState) => ({ ...prevState, profileThresholds: value })),
alwaysAllowUpdateTodoList: state.alwaysAllowUpdateTodoList,
setAlwaysAllowUpdateTodoList: (value) => {
setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value }))
},
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Introduïu prefix de comanda (ex. 'git ')",
"addButton": "Afegir"
},
"updateTodoList": {
"label": "Todo",
"description": "La llista de tasques es actualitza automàticament sense aprovació"
},
"apiRequestLimit": {
"title": "Màximes Sol·licituds",
"description": "Fes aquesta quantitat de sol·licituds API automàticament abans de demanar aprovació per continuar amb la tasca.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Befehlspräfix eingeben (z.B. 'git ')",
"addButton": "Hinzufügen"
},
"updateTodoList": {
"label": "Todo",
"description": "To-Do-Liste wird automatisch aktualisiert, ohne dass du zustimmen musst"
},
"apiRequestLimit": {
"title": "Maximale Anfragen",
"description": "Automatisch so viele API-Anfragen stellen, bevor du um die Erlaubnis gebeten wirst, mit der Aufgabe fortzufahren.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Enter command prefix (e.g., 'git ')",
"addButton": "Add"
},
"updateTodoList": {
"label": "Todo",
"description": "Automatically update the to-do list without requiring approval"
},
"apiRequestLimit": {
"title": "Max Requests",
"description": "Automatically make this many API requests before asking for approval to continue with the task.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Ingrese prefijo de comando (ej. 'git ')",
"addButton": "Añadir"
},
"updateTodoList": {
"label": "Todo",
"description": "La lista de tareas se actualiza automáticamente sin aprobación"
},
"apiRequestLimit": {
"title": "Solicitudes máximas",
"description": "Realizar automáticamente esta cantidad de solicitudes a la API antes de pedir aprobación para continuar con la tarea.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Entrez le préfixe de commande (ex. 'git ')",
"addButton": "Ajouter"
},
"updateTodoList": {
"label": "Todo",
"description": "La liste de tâches est mise à jour automatiquement sans approbation"
},
"apiRequestLimit": {
"title": "Requêtes maximales",
"description": "Effectuer automatiquement ce nombre de requêtes API avant de demander l'approbation pour continuer la tâche.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "कमांड प्रीफिक्स दर्ज करें (उदा. 'git ')",
"addButton": "जोड़ें"
},
"updateTodoList": {
"label": "टूडू",
"description": "अनुमोदन की आवश्यकता के बिना स्वचालित रूप से टूडू सूची अपडेट करें"
},
"apiRequestLimit": {
"title": "अधिकतम अनुरोध",
"description": "कार्य जारी रखने के लिए अनुमति मांगने से पहले स्वचालित रूप से इतने API अनुरोध करें।",

View file

@ -166,6 +166,10 @@
"label": "Tampilkan menu auto-approve di tampilan chat",
"description": "Ketika diaktifkan, menu auto-approve akan ditampilkan di bagian bawah tampilan chat, memungkinkan akses cepat ke pengaturan auto-approve"
},
"updateTodoList": {
"label": "Todo",
"description": "Daftar tugas diperbarui secara otomatis tanpa persetujuan"
},
"apiRequestLimit": {
"title": "Permintaan Maks",
"description": "Secara otomatis membuat sejumlah permintaan API ini sebelum meminta persetujuan untuk melanjutkan tugas.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Inserisci prefisso comando (es. 'git ')",
"addButton": "Aggiungi"
},
"updateTodoList": {
"label": "Todo",
"description": "La lista delle cose da fare viene aggiornata automaticamente senza approvazione"
},
"apiRequestLimit": {
"title": "Richieste massime",
"description": "Esegui automaticamente questo numero di richieste API prima di chiedere l'approvazione per continuare con l'attività.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "コマンドプレフィックスを入力(例:'git '",
"addButton": "追加"
},
"updateTodoList": {
"label": "Todo",
"description": "承認なしで自動的にToDoリストを更新"
},
"apiRequestLimit": {
"title": "最大リクエスト数",
"description": "タスクを続行するための承認を求める前に、自動的にこの数のAPIリクエストを行います。",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "명령 접두사 입력(예: 'git ')",
"addButton": "추가"
},
"updateTodoList": {
"label": "Todo",
"description": "승인 없이 자동으로 할 일 목록이 업데이트됩니다"
},
"apiRequestLimit": {
"title": "최대 요청 수",
"description": "작업을 계속하기 위한 승인을 요청하기 전에 자동으로 이 수의 API 요청을 수행합니다.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Voer commando-prefix in (bijv. 'git ')",
"addButton": "Toevoegen"
},
"updateTodoList": {
"label": "Todo",
"description": "De takenlijst wordt automatisch bijgewerkt zonder goedkeuring"
},
"apiRequestLimit": {
"title": "Maximale verzoeken",
"description": "Voer automatisch dit aantal API-verzoeken uit voordat om goedkeuring wordt gevraagd om door te gaan met de taak.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Wprowadź prefiks polecenia (np. 'git ')",
"addButton": "Dodaj"
},
"updateTodoList": {
"label": "Todo",
"description": "Lista zadań jest automatycznie aktualizowana bez zatwierdzenia"
},
"apiRequestLimit": {
"title": "Maksymalna liczba żądań",
"description": "Automatycznie wykonaj tyle żądań API przed poproszeniem o zgodę na kontynuowanie zadania.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Digite o prefixo do comando (ex. 'git ')",
"addButton": "Adicionar"
},
"updateTodoList": {
"label": "Todo",
"description": "A lista de tarefas é atualizada automaticamente sem aprovação"
},
"apiRequestLimit": {
"title": "Máximo de Solicitações",
"description": "Fazer automaticamente este número de requisições à API antes de pedir aprovação para continuar com a tarefa.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Введите префикс команды (например, 'git ')",
"addButton": "Добавить"
},
"updateTodoList": {
"label": "Todo",
"description": "Список дел обновляется автоматически без подтверждения"
},
"apiRequestLimit": {
"title": "Максимум запросов",
"description": "Автоматически выполнять это количество API-запросов перед запросом разрешения на продолжение задачи.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Komut öneki girin (örn. 'git ')",
"addButton": "Ekle"
},
"updateTodoList": {
"label": "Todo",
"description": "Yapılacaklar listesi onay gerektirmeden otomatik olarak güncellenir"
},
"apiRequestLimit": {
"title": "Maksimum İstek",
"description": "Göreve devam etmek için onay istemeden önce bu sayıda API isteği otomatik olarak yap.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "Nhập tiền tố lệnh (ví dụ: 'git ')",
"addButton": "Thêm"
},
"updateTodoList": {
"label": "Todo",
"description": "Danh sách việc cần làm được cập nhật tự động mà không cần phê duyệt"
},
"apiRequestLimit": {
"title": "Số lượng yêu cầu tối đa",
"description": "Tự động thực hiện số lượng API request này trước khi yêu cầu phê duyệt để tiếp tục với nhiệm vụ.",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "输入命令前缀(例如 'git '",
"addButton": "添加"
},
"updateTodoList": {
"label": "待办",
"description": "无需批准即可自动更新待办清单"
},
"apiRequestLimit": {
"title": "最大请求数",
"description": "在请求批准以继续执行任务之前,自动发出此数量的 API 请求。",

View file

@ -162,6 +162,10 @@
"commandPlaceholder": "輸入命令前綴(例如 'git '",
"addButton": "新增"
},
"updateTodoList": {
"label": "待辦",
"description": "自動更新待辦清單無需批准"
},
"apiRequestLimit": {
"title": "最大請求數",
"description": "在請求批准以繼續執行工作之前,自動發出此數量的 API 請求。",