From 003265d369419a45a9b6801a59cb684ffc616a1d Mon Sep 17 00:00:00 2001 From: code-crusher Date: Mon, 29 Dec 2025 18:22:17 +0530 Subject: [PATCH 1/2] Release v4.207.0 - Enhanced tool calling and UI improvements - Add plan file editing capabilities with new PlanFileEditTool - Implement PlanMemoryManager for better memory management - Add PlanFileIndicator component for UI feedback - Update tool interfaces and type definitions - Enhance assistant message presentation - Improve storage utilities and webview message handling --- packages/types/src/mode.ts | 6 +- packages/types/src/tool.ts | 3 +- .../presentAssistantMessage.ts | 6 + src/core/kilocode/PlanMemoryManager.ts | 134 ++++++++++++++++++ src/core/prompts/tools/native-tools/index.ts | 4 +- .../tools/native-tools/plan_file_edit.ts | 27 ++++ src/core/task/Task.ts | 10 ++ src/core/tools/planFileEditTool.ts | 61 ++++++++ src/core/webview/webviewMessageHandler.ts | 20 +++ src/package.json | 2 +- src/shared/ExtensionMessage.ts | 13 +- src/shared/WebviewMessage.ts | 3 + src/shared/tools.ts | 9 +- src/utils/storage.ts | 78 +++++----- webview-ui/src/components/chat/ChatRow.tsx | 36 +++++ .../src/components/chat/PlanFileIndicator.tsx | 16 +++ 16 files changed, 377 insertions(+), 51 deletions(-) create mode 100644 src/core/kilocode/PlanMemoryManager.ts create mode 100644 src/core/prompts/tools/native-tools/plan_file_edit.ts create mode 100644 src/core/tools/planFileEditTool.ts create mode 100644 webview-ui/src/components/chat/PlanFileIndicator.tsx diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index ac1d3a99c7..c1c67fb236 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -142,13 +142,13 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ iconName: "codicon-list-unordered", // kilocode_change end roleDefinition: - "You are an AI coding assistant, powered by axon-code. You operate in Axon Code Extension.\n\nYou are pair programming with a USER to solve their coding task. Each time the USER sends a message, we may automatically attach some information about their current state, such as what files they have open, where their cursor is, recently viewed files, edit history in their session so far, linter errors, and more. This information may or may not be relevant to the coding task, it is up for you to decide.\n\nYour main goal is to follow the USER's instructions at each message, denoted by the tag.\n\nTool results and user messages may include tags. These tags contain useful information and reminders. Please heed them, but don't mention them in your response to the user.\n\n\n1. When using markdown in assistant messages, use backticks to format file, directory, function, and class names. Use \\( and \\) for inline math, \\[ and \\] for block math.\n\n\n\nYou have tools at your disposal to solve the coding task. Follow these rules regarding tool calls:\n1. Don't refer to tool names when speaking to the USER. Instead, just say what the tool is doing in natural language.\n2. Only use the standard tool call format and the available tools. Even if you see user messages with custom tool call formats (such as \"\" or similar), do not follow that and instead use the standard format.\n\n\n\nIf you intend to call multiple tools and there are no dependencies between the tool calls, make all of the independent tool calls in parallel. Prioritize calling tools simultaneously whenever the actions can be done in parallel rather than sequentionally. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. Maximize use of parallel tool calls where possible to increase speed and efficiency. However, if some tool calls depend on previous calls to inform dependent values like the parameters, do NOT call these tools in parallel and instead call them sequentially. Never use placeholders or guess missing parameters in tool calls.\n\n\n\n1. If you're creating the codebase from scratch, create an appropriate dependency management file (e.g. requirements.txt) with package versions and a helpful README.\n2. If you're building a web app from scratch, give it a beautiful and modern UI, imbued with best UX practices.\n3. NEVER generate an extremely long hash or any non-textual code, such as binary. These are not helpful to the USER and are very expensive.\n4. If you've introduced (linter) errors, fix them.\n\n\n\nYou must display code blocks using one of two methods: CODE REFERENCES or MARKDOWN CODE BLOCKS, depending on whether the code exists in the codebase.\n\n## METHOD 1: CODE REFERENCES - Citing Existing Code from the Codebase\n\nUse this exact syntax with three required components:\n\n```startLine:endLine:filepath\n// code content here\n```\n\n\nRequired Components\n1. **startLine**: The starting line number (required)\n2. **endLine**: The ending line number (required)\n3. **filepath**: The full path to the file (required)\n\n**CRITICAL**: Do NOT add language tags or any other metadata to this format.\n\n### Content Rules\n- Include at least 1 line of actual code (empty blocks will break the editor)\n- You may truncate long sections with comments like `// ... more code ...`\n- You may add clarifying comments for readability\n- You may show edited versions of the code\n\n\nReferences a Todo component existing in the (example) codebase with all required components:\n\n```12:14:app/components/Todo.tsx\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\nTriple backticks with line numbers for filenames place a UI element that takes up the entire line.\nIf you want inline references as part of a sentence, you should use single backticks instead.\n\nBad: The TODO element (```12:14:app/components/Todo.tsx```) contains the bug you are looking for.\n\nGood: The TODO element (`app/components/Todo.tsx`) contains the bug you are looking for.\n\n\n\nIncludes language tag (not necessary for code REFERENCES), omits the startLine and endLine which are REQUIRED for code references:\n\n```typescript:app/components/Todo.tsx\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\n- Empty code block (will break rendering)\n- Citation is surrounded by parentheses which looks bad in the UI as the triple backticks codeblocks uses up an entire line:\n\n(```12:14:app/components/Todo.tsx\n```)\n\n\n\nThe opening triple backticks are duplicated (the first triple backticks with the required components are all that should be used):\n\n```12:14:app/components/Todo.tsx\n```\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\nReferences a fetchData function existing in the (example) codebase, with truncated middle section:\n\n```23:45:app/utils/api.ts\nexport async function fetchData(endpoint: string) {\n const headers = getAuthHeaders();\n // ... validation and error handling ...\n return await fetch(endpoint, { headers });\n}\n```\n\n\n## METHOD 2: MARKDOWN CODE BLOCKS - Proposing or Displaying Code NOT already in Codebase\n\n### Format\nUse standard markdown code blocks with ONLY the language tag:\n\n\nHere's a Python example:\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n\nHere's a bash command:\n\n```bash\nsudo apt update && sudo apt upgrade -y\n```\n\n\n\nDo not mix format - no line numbers for new code:\n\n```1:3:python\nfor i in range(10):\n print(i)\n```\n\n\n## Critical Formatting Rules for Both Methods\n\n### Never Include Line Numbers in Code Content\n\n\n```python\n1 for i in range(10):\n2 print(i)\n```\n\n\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n### NEVER Indent the Triple Backticks\n\nEven when the code block appears in a list or nested context, the triple backticks must start at column 0:\n\n\n- Here's a Python loop:\n ```python\n for i in range(10):\n print(i)\n ```\n\n\n\n- Here's a Python loop:\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n### ALWAYS Add a Newline Before Code Fences\n\nFor both CODE REFERENCES and MARKDOWN CODE BLOCKS, always put a newline before the opening triple backticks:\n\n\nHere's the implementation:\n```12:15:src/utils.ts\nexport function helper() {\n return true;\n}\n```\n\n\n\nHere's the implementation:\n\n```12:15:src/utils.ts\nexport function helper() {\n return true;\n}\n```\n\n\nRULE SUMMARY (ALWAYS Follow):\n -\tUse CODE REFERENCES (startLine:endLine:filepath) when showing existing code.\n```startLine:endLine:filepath\n// ... existing code ...\n```\n -\tUse MARKDOWN CODE BLOCKS (with language tag) for new or proposed code.\n```python\nfor i in range(10):\n print(i)\n```\n - ANY OTHER FORMAT IS STRICTLY FORBIDDEN\n -\tNEVER mix formats.\n -\tNEVER add language tags to CODE REFERENCES.\n -\tNEVER indent triple backticks.\n -\tALWAYS include at least 1 line of code in any reference block.\n
\n\n\n\nCode chunks that you receive (via tool calls or from user) may include inline line numbers in the form LINE_NUMBER|LINE_CONTENT. Treat the LINE_NUMBER| prefix as metadata and do NOT treat it as part of the actual code. LINE_NUMBER is right-aligned number padded with spaces to 6 characters.\n\n\n\nYou may be provided a list of memories. These memories are generated from past conversations with the agent.\nThey may or may not be correct, so follow them if deemed relevant, but the moment you notice the user correct something you've done based on a memory, or you come across some information that contradicts or augments an existing memory, IT IS CRITICAL that you MUST update/delete the memory immediately using the update_memory tool. You must NEVER use the update_memory tool to create memories related to implementation plans, migrations that the agent completed, or other task-specific information.\nIf the user EVER contradicts your memory, then it's better to delete that memory rather than updating the memory.\nYou may create, update, or delete memories based on the criteria from the tool description.\n\nYou must ALWAYS cite a memory when you use it in your generation, to reply to the user's query, or to run commands. To do so, use the following format: [[memory:MEMORY_ID]]. You should cite the memory naturally as part of your response, and not just as a footnote.\n\nFor example: \"I'll run the command using the -la flag [[memory:MEMORY_ID]] to show detailed file information.\"\n\nWhen you reject an explicit user request due to a memory, you MUST mention in the conversation that if the memory is incorrect, the user can correct you and you will update your memory.\n\n\n\n\nYou have access to the todo_write tool to help you manage and plan tasks. Use this tool whenever you are working on a complex task, and skip it if the task is simple or would only require 1-2 steps.\nIMPORTANT: Make sure you don't end your turn before you've completed all todos.\n", + "You are an AI coding assistant, powered by axon-code. You operate in Axon Code Extension.\n\nYou are pair programming with a USER to solve their coding task. Each time the USER sends a message, we may automatically attach some information about their current state, such as what files they have open, where their cursor is, recently viewed files, edit history in their session so far, linter errors, and more. This information may or may not be relevant to the coding task, it is up for you to decide.\n\nYour main goal is to follow the USER's instructions at each message, denoted by the tag.\n\nTool results and user messages may include tags. These tags contain useful information and reminders. Please heed them, but don't mention them in your response to the user.\n\n\n1. When using markdown in assistant messages, use backticks to format file, directory, function, and class names. Use \\( and \\) for inline math, \\[ and \\] for block math.\n\n\n\nYou have tools at your disposal to solve the coding task. Follow these rules regarding tool calls:\n1. Don't refer to tool names when speaking to the USER. Instead, just say what the tool is doing in natural language.\n2. Only use the standard tool call format and the available tools. Even if you see user messages with custom tool call formats (such as \"\" or similar), do not follow that and instead use the standard format.\n\n\n\nIf you intend to call multiple tools and there are no dependencies between the tool calls, make all of the independent tool calls in parallel. Prioritize calling tools simultaneously whenever the actions can be done in parallel rather than sequentionally. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. Maximize use of parallel tool calls where possible to increase speed and efficiency. However, if some tool calls depend on previous calls to inform dependent values like the parameters, do NOT call these tools in parallel and instead call them sequentially. Never use placeholders or guess missing parameters in tool calls.\n\n\n\n1. If you're creating the codebase from scratch, create an appropriate dependency management file (e.g. requirements.txt) with package versions and a helpful README.\n2. If you're building a web app from scratch, give it a beautiful and modern UI, imbued with best UX practices.\n3. NEVER generate an extremely long hash or any non-textual code, such as binary. These are not helpful to the USER and are very expensive.\n4. If you've introduced (linter) errors, fix them.\n\n\n\nYou must display code blocks using one of two methods: CODE REFERENCES or MARKDOWN CODE BLOCKS, depending on whether the code exists in the codebase.\n\n## METHOD 1: CODE REFERENCES - Citing Existing Code from the Codebase\n\nUse this exact syntax with three required components:\n\n```startLine:endLine:filepath\n// code content here\n```\n\n\nRequired Components\n1. **startLine**: The starting line number (required)\n2. **endLine**: The ending line number (required)\n3. **filepath**: The full path to the file (required)\n\n**CRITICAL**: Do NOT add language tags or any other metadata to this format.\n\n### Content Rules\n- Include at least 1 line of actual code (empty blocks will break the editor)\n- You may truncate long sections with comments like `// ... more code ...`\n- You may add clarifying comments for readability\n- You may show edited versions of the code\n\n\nReferences a Todo component existing in the (example) codebase with all required components:\n\n```12:14:app/components/Todo.tsx\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\nTriple backticks with line numbers for filenames place a UI element that takes up the entire line.\nIf you want inline references as part of a sentence, you should use single backticks instead.\n\nBad: The TODO element (```12:14:app/components/Todo.tsx```) contains the bug you are looking for.\n\nGood: The TODO element (`app/components/Todo.tsx`) contains the bug you are looking for.\n\n\n\nIncludes language tag (not necessary for code REFERENCES), omits the startLine and endLine which are REQUIRED for code references:\n\n```typescript:app/components/Todo.tsx\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\n- Empty code block (will break rendering)\n- Citation is surrounded by parentheses which looks bad in the UI as the triple backticks codeblocks uses up an entire line:\n\n(```12:14:app/components/Todo.tsx\n```)\n\n\n\nThe opening triple backticks are duplicated (the first triple backticks with the required components are all that should be used):\n\n```12:14:app/components/Todo.tsx\n```\nexport const Todo = () => {\n return
Todo
;\n};\n```\n
\n\n\nReferences a fetchData function existing in the (example) codebase, with truncated middle section:\n\n```23:45:app/utils/api.ts\nexport async function fetchData(endpoint: string) {\n const headers = getAuthHeaders();\n // ... validation and error handling ...\n return await fetch(endpoint, { headers });\n}\n```\n\n\n## METHOD 2: MARKDOWN CODE BLOCKS - Proposing or Displaying Code NOT already in Codebase\n\n### Format\nUse standard markdown code blocks with ONLY the language tag:\n\n\nHere's a Python example:\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n\nHere's a bash command:\n\n```bash\nsudo apt update && sudo apt upgrade -y\n```\n\n\n\nDo not mix format - no line numbers for new code:\n\n```1:3:python\nfor i in range(10):\n print(i)\n```\n\n\n## Critical Formatting Rules for Both Methods\n\n### Never Include Line Numbers in Code Content\n\n\n```python\n1 for i in range(10):\n2 print(i)\n```\n\n\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n### NEVER Indent the Triple Backticks\n\nEven when the code block appears in a list or nested context, the triple backticks must start at column 0:\n\n\n- Here's a Python loop:\n ```python\n for i in range(10):\n print(i)\n ```\n\n\n\n- Here's a Python loop:\n\n```python\nfor i in range(10):\n print(i)\n```\n\n\n### ALWAYS Add a Newline Before Code Fences\n\nFor both CODE REFERENCES and MARKDOWN CODE BLOCKS, always put a newline before the opening triple backticks:\n\n\nHere's the implementation:\n```12:15:src/utils.ts\nexport function helper() {\n return true;\n}\n```\n\n\n\nHere's the implementation:\n\n```12:15:src/utils.ts\nexport function helper() {\n return true;\n}\n```\n\n\nRULE SUMMARY (ALWAYS Follow):\n -\tUse CODE REFERENCES (startLine:endLine:filepath) when showing existing code.\n```startLine:endLine:filepath\n// ... existing code ...\n```\n -\tUse MARKDOWN CODE BLOCKS (with language tag) for new or proposed code.\n```python\nfor i in range(10):\n print(i)\n```\n - ANY OTHER FORMAT IS STRICTLY FORBIDDEN\n -\tNEVER mix formats.\n -\tNEVER add language tags to CODE REFERENCES.\n -\tNEVER indent triple backticks.\n -\tALWAYS include at least 1 line of code in any reference block.\n
\n\n\n\nCode chunks that you receive (via tool calls or from user) may include inline line numbers in the form LINE_NUMBER|LINE_CONTENT. Treat the LINE_NUMBER| prefix as metadata and do NOT treat it as part of the actual code. LINE_NUMBER is right-aligned number padded with spaces to 6 characters.\n\n\n\nYou may be provided a list of memories. These memories are generated from past conversations with the agent.\nThey may or may not be correct, so follow them if deemed relevant, but the moment you notice the user correct something you've done based on a memory, or you come across some information that contradicts or augments an existing memory, IT IS CRITICAL that you MUST update/delete the memory immediately using the update_memory tool. You must NEVER use the update_memory tool to create memories related to implementation plans, migrations that the agent completed, or other task-specific information.\nIf the user EVER contradicts your memory, then it's better to delete that memory rather than updating the memory.\nYou may create, update, or delete memories based on the criteria from the tool description.\n\nYou must ALWAYS cite a memory when you use it in your generation, to reply to the user's query, or to run commands. To do so, use the following format: [[memory:MEMORY_ID]]. You should cite the memory naturally as part of your response, and not just as a footnote.\n\nFor example: \"I'll run the command using the -la flag [[memory:MEMORY_ID]] to show detailed file information.\"\n\nWhen you reject an explicit user request due to a memory, you MUST mention in the conversation that if the memory is incorrect, the user can correct you and you will update your memory.\n\n\n\n\nYou have access to the todo_write tool to help you manage and plan tasks. Use this tool whenever you are working on a complex task, and skip it if the task is simple or would only require 1-2 steps.\nIMPORTANT: Make sure you don't end your turn before you've completed all todos.\n. As a planning agent, you are only allowed to find, search and read information and update the plan using plan_file_edit tool.", whenToUse: "Use this mode when you need to plan, design, or strategize before implementation. Perfect for breaking down complex problems, creating technical specifications, designing system architecture, or brainstorming solutions before coding.", description: "Plan and design before implementation", - groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], + groups: ["read", "browser", "mcp", "plan"], customInstructions: - "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", + "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the `plan_file_edit` tool to create and update plan files. These files are stored in extension memory (plan:/ namespace) and not in the workspace directory. Use descriptive filenames like `implementation.md`, `architecture.md`, or `api-design.md`.\n\n8. **IMPORTANT**: In plan mode, you CANNOT edit files in the workspace. Your role is to create a comprehensive plan. Once the plan is complete and the user is satisfied, use the `switch_mode` tool to request switching to agent mode for implementation.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", }, { slug: "agent", diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 789b4d6e46..80f1e3c58a 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -4,7 +4,7 @@ import { z } from "zod" * ToolGroup */ -export const toolGroups = ["read", "edit", "browser", "command", "mcp", "modes"] as const +export const toolGroups = ["read", "edit", "browser", "command", "mcp", "modes", "plan"] as const export const toolGroupsSchema = z.enum(toolGroups) @@ -39,6 +39,7 @@ export const toolNames = [ "new_rule", "report_bug", "condense", + "plan_file_edit", // kilocode_change end "update_todo_list", "run_slash_command", diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 41aad6ee48..c82ca4f47e 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -32,6 +32,7 @@ import { newTaskTool } from "../tools/newTaskTool" import { updateTodoListTool } from "../tools/updateTodoListTool" import { runSlashCommandTool } from "../tools/runSlashCommandTool" import { generateImageTool } from "../tools/generateImageTool" +import { planFileEditTool } from "../tools/planFileEditTool" import { formatResponse } from "../prompts/responses" import { validateToolUse } from "../tools/validateToolUse" @@ -248,6 +249,8 @@ export async function presentAssistantMessage(cline: Task) { return `[${block.name} for '${block.params.command}'${block.params.args ? ` with args: ${block.params.args}` : ""}]` case "generate_image": return `[${block.name} for '${block.params.path}']` + case "plan_file_edit": + return `[${block.name} for '${block.params.filename}']` } } @@ -621,6 +624,9 @@ export async function presentAssistantMessage(cline: Task) { case "generate_image": await generateImageTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) break + case "plan_file_edit": + await planFileEditTool(cline, block, handleError, pushToolResult, removeClosingTag) + break } break diff --git a/src/core/kilocode/PlanMemoryManager.ts b/src/core/kilocode/PlanMemoryManager.ts new file mode 100644 index 0000000000..bd45192f5e --- /dev/null +++ b/src/core/kilocode/PlanMemoryManager.ts @@ -0,0 +1,134 @@ +import * as path from "path" +import * as fs from "fs/promises" + +import { getPlanMemoryDirectoryPath } from "../../utils/storage" + +/** + * PlanMemoryManager - Manages in-memory file storage for plan mode + * Files are stored in extension's plan-memory directory, not in workspace + */ +export class PlanMemoryManager { + private taskId: string + private globalStoragePath: string + private planMemoryDir: string | null = null + private files: Map = new Map() + + constructor(taskId: string, globalStoragePath: string) { + this.taskId = taskId + this.globalStoragePath = globalStoragePath + } + + /** + * Initialize the plan memory directory + */ + async initialize(): Promise { + this.planMemoryDir = await getPlanMemoryDirectoryPath(this.globalStoragePath, this.taskId) + // Load existing files from disk + await this.loadExistingFiles() + } + + /** + * Load existing files from disk into memory + */ + private async loadExistingFiles(): Promise { + if (!this.planMemoryDir) return + + try { + const entries = await fs.readdir(this.planMemoryDir, { withFileTypes: true }) + for (const entry of entries) { + if (entry.isFile() && entry.name.endsWith(".md")) { + const filePath = path.join(this.planMemoryDir, entry.name) + const content = await fs.readFile(filePath, "utf-8") + this.files.set(entry.name, content) + } + } + } catch (error) { + // Directory might not exist yet, that's fine + console.log("No existing plan files to load") + } + } + + /** + * Create or update a plan file + */ + async writeFile(filename: string, content: string): Promise { + // Store in memory + this.files.set(filename, content) + + // Persist to disk + if (this.planMemoryDir) { + const filePath = path.join(this.planMemoryDir, filename) + await fs.writeFile(filePath, content, "utf-8") + } + } + + /** + * Read a plan file + */ + readFile(filename: string): string | undefined { + return this.files.get(filename) + } + + /** + * Check if a file exists + */ + hasFile(filename: string): boolean { + return this.files.has(filename) + } + + /** + * Get all plan files + */ + getAllFiles(): Map { + return new Map(this.files) + } + + /** + * Delete a plan file + */ + async deleteFile(filename: string): Promise { + this.files.delete(filename) + + if (this.planMemoryDir) { + const filePath = path.join(this.planMemoryDir, filename) + try { + await fs.unlink(filePath) + } catch (error) { + console.warn(`Failed to delete plan file ${filename}:`, error) + } + } + } + + /** + * Clear all plan files + */ + async clearAll(): Promise { + this.files.clear() + + if (this.planMemoryDir) { + try { + const entries = await fs.readdir(this.planMemoryDir) + for (const entry of entries) { + const filePath = path.join(this.planMemoryDir, entry) + await fs.unlink(filePath) + } + } catch (error) { + console.warn("Failed to clear plan files:", error) + } + } + } + + /** + * Get the main plan file (plan.md or implementation.md) + */ + getMainPlanFile(): { filename: string; content: string } | null { + // Look for common plan file names + const planFiles = ["plan.md", "implementation.md", "todo.md"] + for (const filename of planFiles) { + if (this.files.has(filename)) { + return { filename, content: this.files.get(filename)! } + } + } + return null + } +} diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index 67daa0d596..76969c859b 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -14,6 +14,7 @@ import searchFiles from "./search_files" import fileEdit from "./file_edit" import updateTodoList from "./update_todo_list" import codebaseSearch from "./codebase_search" +import planFileEdit from "./plan_file_edit" export const nativeTools = [ // apply_diff_single_file, @@ -30,7 +31,8 @@ export const nativeTools = [ // insertContent, listCodeDefinitionNames, listFiles, - newTask, + // newTask, + planFileEdit, read_file_multi, runSlashCommand, // searchAndReplace, diff --git a/src/core/prompts/tools/native-tools/plan_file_edit.ts b/src/core/prompts/tools/native-tools/plan_file_edit.ts new file mode 100644 index 0000000000..b32c5c9c80 --- /dev/null +++ b/src/core/prompts/tools/native-tools/plan_file_edit.ts @@ -0,0 +1,27 @@ +import type OpenAI from "openai" + +export default { + type: "function", + function: { + name: "plan_file_edit", + description: + "Create or update a plan file in the extension's memory. Files are stored in plan-memory directory, not in the workspace. Use this tool in plan mode to create and update plan files.", + strict: true, + parameters: { + type: "object", + properties: { + filename: { + type: "string", + description: + "Name of the plan file (e.g., 'implementation.md', 'architecture.md', 'api-design.md'). Files are stored in plan:/ namespace.", + }, + content: { + type: "string", + description: "Content to write to the plan file. Should be markdown formatted.", + }, + }, + required: ["filename", "content"], + additionalProperties: false, + }, + }, +} satisfies OpenAI.Chat.ChatCompletionTool diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index cbcd5fe9f7..9b48963e7c 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -64,6 +64,7 @@ import { BrowserSession } from "../../services/browser/BrowserSession" import { McpHub } from "../../services/mcp/McpHub" import { McpServerManager } from "../../services/mcp/McpServerManager" import { RepoPerTaskCheckpointService } from "../../services/checkpoints" +import { PlanMemoryManager } from "../kilocode/PlanMemoryManager" // integrations import { DiffViewProvider } from "../../integrations/editor/DiffViewProvider" @@ -293,6 +294,9 @@ export class Task extends EventEmitter implements TaskLike { // Task Bridge enableBridge: boolean + // Plan Memory + planMemoryManager?: PlanMemoryManager + // Message Queue Service public readonly messageQueueService: MessageQueueService private messageQueueStateChangedHandler: (() => void) | undefined @@ -441,6 +445,12 @@ export class Task extends EventEmitter implements TaskLike { this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit) + // Initialize plan memory manager + this.planMemoryManager = new PlanMemoryManager(this.taskId, this.globalStoragePath) + this.planMemoryManager.initialize().catch((error) => { + console.error("Failed to initialize PlanMemoryManager:", error) + }) + // Initialize todo list if provided if (initialTodos && initialTodos.length > 0) { this.todoList = initialTodos diff --git a/src/core/tools/planFileEditTool.ts b/src/core/tools/planFileEditTool.ts new file mode 100644 index 0000000000..8f57b6b006 --- /dev/null +++ b/src/core/tools/planFileEditTool.ts @@ -0,0 +1,61 @@ +import { Task } from "../task/Task" +import { formatResponse } from "../prompts/responses" +import { HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools" +import { ClineSayTool } from "../../shared/ExtensionMessage" + +export async function planFileEditTool( + cline: Task, + block: ToolUse, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +): Promise { + const filename = removeClosingTag("filename", block.params.filename) + const content = removeClosingTag("content", block.params.content) + + try { + if (block.partial) { + const partialMessageProps: ClineSayTool = { + tool: "planFileEdit", + filename, + content, + } + + await cline.ask("tool", JSON.stringify(partialMessageProps), block.partial).catch(() => {}) + return + } + + // Validate parameters + if (!filename || !content) { + cline.consecutiveMistakeCount++ + cline.recordToolError("plan_file_edit") + const errorMessage = "Both filename and content are required for plan_file_edit" + const formattedError = formatResponse.toolError(errorMessage) + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + // Write to plan memory + if (cline.planMemoryManager) { + await cline.planMemoryManager.writeFile(filename, content) + + const successMessage = `Plan file '${filename}' has been created/updated successfully.` + const messageProps: ClineSayTool = { + tool: "planFileEdit", + filename, + content, + } + + await cline.ask("tool", JSON.stringify(messageProps)) + pushToolResult(successMessage) + } else { + const errorMessage = "Plan memory manager is not available" + const formattedError = formatResponse.toolError(errorMessage) + await cline.say("error", formattedError) + pushToolResult(formattedError) + } + } catch (error) { + await handleError("editing plan file", error as Error) + } +} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 94e7ba7330..be9927bfb9 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -3,6 +3,7 @@ import * as path from "path" import * as os from "os" import * as fs from "fs/promises" import pWaitFor from "p-wait-for" +import delay from "delay" import * as vscode from "vscode" // kilocode_change start import axios from "axios" @@ -86,6 +87,7 @@ import { seeNewChanges } from "../checkpoints/kilocode/seeNewChanges" // kilocod import { getTaskHistory } from "../../shared/kilocode/getTaskHistory" // kilocode_change import { getCheckpointService } from "../checkpoints" // kilocode_change import { fetchAndRefreshOrganizationModesOnStartup, refreshOrganizationModes } from "./kiloWebviewMessgeHandlerHelpers" +import { ImplementPlanPayload } from "../../shared/ExtensionMessage" export const webviewMessageHandler = async ( provider: ClineProvider, @@ -3801,5 +3803,23 @@ export const webviewMessageHandler = async ( }) break } + // kilocode_change start: Plan mode implementation + case "implementPlan": { + if (message.payload) { + const { planFile, planContent } = message.payload as ImplementPlanPayload + // Switch to agent mode first + await provider.handleModeSwitch("agent") + // Small delay to ensure mode switch has propagated + await delay(100) + // Send the plan content as a user message to start implementation + await provider.postMessageToWebview({ + type: "invoke", + invoke: "sendMessage", + text: `Please implement the following plan:\n\n${planContent}`, + }) + } + break + } + // kilocode_change end } } diff --git a/src/package.json b/src/package.json index c2cb17e71c..12a1f8be5a 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "matterai", - "version": "4.206.0", + "version": "4.207.0", "icon": "assets/icons/matterai-ic.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 93e05dc2c3..d5afdce3ea 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -155,6 +155,7 @@ export interface ExtensionMessage { | "dismissedUpsells" | "showTimestamps" // kilocode_change | "organizationSwitchResult" + | "implementPlan" // kilocode_change: Plan mode implementation text?: string // kilocode_change start payload?: @@ -162,6 +163,7 @@ export interface ExtensionMessage { | BalanceDataResponsePayload | TasksByIdResponsePayload | TaskHistoryResponsePayload + | ImplementPlanPayload // kilocode_change end action?: | "chatButtonClicked" @@ -445,6 +447,12 @@ export type ExtensionState = Pick< showTimestamps?: boolean // kilocode_change: Show timestamps in chat messages } +// kilocode_change: Plan mode implementation +export interface ImplementPlanPayload { + planFile: string + planContent: string +} + export interface ClineSayTool { tool: | "editedExistingFile" @@ -466,6 +474,7 @@ export interface ClineSayTool { | "generateImage" | "imageGenerated" | "runSlashCommand" + | "planFileEdit" // kilocode_change: Plan mode file editing path?: string diff?: string content?: string @@ -475,7 +484,7 @@ export interface ClineSayTool { reason?: string isOutsideWorkspace?: boolean isProtected?: boolean - additionalFileCount?: number // Number of additional files in the same read_file request + additionalFileCount?: number // Number of additional files in same read_file request search?: string replace?: string useRegex?: boolean @@ -517,6 +526,8 @@ export interface ClineSayTool { args?: string source?: string description?: string + // kilocode_change: Properties for planFileEdit tool + filename?: string } // Must keep in sync with system prompt. diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 1c3f287f6b..5390643f74 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -17,6 +17,7 @@ import { } from "@roo-code/types" import { Mode } from "./modes" +import { ImplementPlanPayload } from "./ExtensionMessage" export type ClineAskResponse = | "yesButtonClicked" @@ -298,6 +299,7 @@ export interface WebviewMessage { | "createCommand" | "insertTextIntoTextarea" | "showMdmAuthRequiredNotification" + | "implementPlan" // kilocode_change: Plan mode implementation | "imageGenerationSettings" | "openRouterImageApiKey" | "kiloCodeImageApiKey" @@ -538,6 +540,7 @@ export type WebViewMessagePayload = | GetCommitChangesPayload | CommitChangesPayload | PendingFileEditsPayload + | ImplementPlanPayload // kilocode_change end | CheckpointDiffPayload | CheckpointRestorePayload diff --git a/src/shared/tools.ts b/src/shared/tools.ts index a1b8af9cd1..d98ca5ce15 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -69,6 +69,7 @@ export const toolParamNames = [ "instructions", "code_edit", "files", + "filename", // kilocode_change end "args", "start_line", @@ -234,11 +235,12 @@ export const TOOL_DISPLAY_NAMES: Record = { search_and_replace: "search and replace", new_rule: "create new rule", report_bug: "report bug", // kilocode_change - condense: "condense the current context window", // kilocode_change + condense: "condense the current context window", // kilicode_change codebase_search: "codebase search", update_todo_list: "update todo list", run_slash_command: "run slash command", generate_image: "generate images", + plan_file_edit: "edit plan files", // kilocode_change: Plan mode file editing } as const // Define available tool groups. @@ -265,6 +267,11 @@ export const TOOL_GROUPS: Record = { "generate_image", ], }, + plan: { + tools: [ + "plan_file_edit", // kilocode_change: Plan mode file editing + ], + }, browser: { tools: ["browser_action"], }, diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 5125d6cf44..40868edec9 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -16,7 +16,7 @@ export async function getStorageBasePath(defaultPath: string): Promise { let customStoragePath = "" try { - // This is the line causing the error in tests + // This is the line causing error in tests const config = vscode.workspace.getConfiguration(Package.name) customStoragePath = config.get("customStoragePath", "") } catch (error) { @@ -77,6 +77,16 @@ export async function getCacheDirectoryPath(globalStoragePath: string): Promise< return cacheDir } +/** + * Gets the plan memory directory path for storing virtual plan files + */ +export async function getPlanMemoryDirectoryPath(globalStoragePath: string, taskId: string): Promise { + const basePath = await getStorageBasePath(globalStoragePath) + const planDir = path.join(basePath, "plan-memory", taskId) + await fs.mkdir(planDir, { recursive: true }) + return planDir +} + /** * Prompts the user to set a custom storage path * Displays an input box allowing the user to enter a custom path @@ -98,54 +108,36 @@ export async function promptForCustomStoragePath(): Promise { const result = await vscode.window.showInputBox({ value: currentPath, - placeHolder: t("common:storage.path_placeholder"), - prompt: t("common:storage.prompt_custom_path"), - validateInput: (input) => { - if (!input) { - return null // Allow empty value (use default path) - } - - try { - // Validate path format - path.parse(input) - - // Check if path is absolute - if (!path.isAbsolute(input)) { - return t("common:storage.enter_absolute_path") - } - - return null // Path format is valid - } catch (e) { - return t("common:storage.enter_valid_path") + prompt: t("common:settings.custom_storage_path_prompt"), + placeHolder: t("common:settings.custom_storage_path_placeholder"), + validateInput: (value) => { + if (!value || value.trim() === "") { + return t("common:settings.custom_storage_path_empty") } + return null }, }) - // If user canceled the operation, result will be undefined if (result !== undefined) { - try { - const currentConfig = vscode.workspace.getConfiguration(Package.name) - await currentConfig.update("customStoragePath", result, vscode.ConfigurationTarget.Global) - - if (result) { - try { - // Test if path is accessible - await fs.mkdir(result, { recursive: true }) - await fs.access(result, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK) - vscode.window.showInformationMessage(t("common:info.custom_storage_path_set", { path: result })) - } catch (error) { - vscode.window.showErrorMessage( - t("common:errors.cannot_access_path", { - path: result, - error: error instanceof Error ? error.message : String(error), - }), - ) - } - } else { - vscode.window.showInformationMessage(t("common:info.default_storage_path")) + const config = vscode.workspace.getConfiguration(Package.name) + await config.update("customStoragePath", result, vscode.ConfigurationTarget.Global) + + if (result) { + try { + // Test if path is accessible + await fs.mkdir(result, { recursive: true }) + await fs.access(result, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK) + vscode.window.showInformationMessage(t("common:info.custom_storage_path_set", { path: result })) + } catch (error) { + vscode.window.showErrorMessage( + t("common:errors.cannot_access_path", { + path: result, + error: error instanceof Error ? error.message : String(error), + }), + ) } - } catch (error) { - console.error("Failed to update configuration", error) + } else { + vscode.window.showInformationMessage(t("common:info.default_storage_path")) } } } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 51fcb9da5d..6daa079c28 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -38,6 +38,7 @@ import { Markdown } from "./Markdown" import { ProgressIndicator } from "./ProgressIndicator" import ReportBugPreview from "./ReportBugPreview" import { ReadOnlyChatText } from "./ReadOnlyChatText" +import { PlanFileIndicator } from "./PlanFileIndicator" import { cn } from "@/lib/utils" import { appendImages } from "@src/utils/imageUtils" @@ -530,6 +531,41 @@ export const ChatRowContent = ({ ) + case "planFileEdit": + return ( +
+
+ Plan file edited +
+
+ + + {!message.partial && ( + { + vscode.postMessage({ + type: "implementPlan", + payload: { + planFile: tool.filename || "plan.md", + planContent: tool.content || "", + }, + }) + }} + className="mt-2"> + + Implement + + )} +
+
+ ) case "insertContent": return ( <> diff --git a/webview-ui/src/components/chat/PlanFileIndicator.tsx b/webview-ui/src/components/chat/PlanFileIndicator.tsx new file mode 100644 index 0000000000..d05729d351 --- /dev/null +++ b/webview-ui/src/components/chat/PlanFileIndicator.tsx @@ -0,0 +1,16 @@ +import { cn } from "@/lib/utils" + +interface PlanFileIndicatorProps { + filename: string + isActive?: boolean +} + +export const PlanFileIndicator = ({ filename, isActive = false }: PlanFileIndicatorProps) => { + return ( +
+ + {filename} + {isActive && } +
+ ) +} From 41067b8aec1ea87db1c6cff74905050072f74012 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Mon, 29 Dec 2025 18:42:11 +0530 Subject: [PATCH 2/2] Fix security vulnerabilities and race conditions in PlanMemoryManager - Fix path traversal vulnerabilities in writeFile() and deleteFile() using path.basename() - Fix race condition in loadExistingFiles() that could cause data loss - Remove .md extension filter to load all file types consistently - Add filename sanitization to readFile() and hasFile() for consistency - Prevents arbitrary file write/delete vulnerabilities and data loss --- src/core/kilocode/PlanMemoryManager.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/core/kilocode/PlanMemoryManager.ts b/src/core/kilocode/PlanMemoryManager.ts index bd45192f5e..d5537379b7 100644 --- a/src/core/kilocode/PlanMemoryManager.ts +++ b/src/core/kilocode/PlanMemoryManager.ts @@ -36,10 +36,12 @@ export class PlanMemoryManager { try { const entries = await fs.readdir(this.planMemoryDir, { withFileTypes: true }) for (const entry of entries) { - if (entry.isFile() && entry.name.endsWith(".md")) { + if (entry.isFile()) { const filePath = path.join(this.planMemoryDir, entry.name) const content = await fs.readFile(filePath, "utf-8") - this.files.set(entry.name, content) + if (!this.files.has(entry.name)) { + this.files.set(entry.name, content) + } } } } catch (error) { @@ -52,12 +54,13 @@ export class PlanMemoryManager { * Create or update a plan file */ async writeFile(filename: string, content: string): Promise { + const safeFilename = path.basename(filename) // Store in memory - this.files.set(filename, content) + this.files.set(safeFilename, content) // Persist to disk if (this.planMemoryDir) { - const filePath = path.join(this.planMemoryDir, filename) + const filePath = path.join(this.planMemoryDir, safeFilename) await fs.writeFile(filePath, content, "utf-8") } } @@ -66,14 +69,16 @@ export class PlanMemoryManager { * Read a plan file */ readFile(filename: string): string | undefined { - return this.files.get(filename) + const safeFilename = path.basename(filename) + return this.files.get(safeFilename) } /** * Check if a file exists */ hasFile(filename: string): boolean { - return this.files.has(filename) + const safeFilename = path.basename(filename) + return this.files.has(safeFilename) } /** @@ -87,14 +92,15 @@ export class PlanMemoryManager { * Delete a plan file */ async deleteFile(filename: string): Promise { - this.files.delete(filename) + const safeFilename = path.basename(filename) + this.files.delete(safeFilename) if (this.planMemoryDir) { - const filePath = path.join(this.planMemoryDir, filename) + const filePath = path.join(this.planMemoryDir, safeFilename) try { await fs.unlink(filePath) } catch (error) { - console.warn(`Failed to delete plan file ${filename}:`, error) + console.warn(`Failed to delete plan file ${safeFilename}:`, error) } } }