Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/types/src/mode.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/types/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -39,6 +39,7 @@ export const toolNames = [
"new_rule",
"report_bug",
"condense",
"plan_file_edit",
// kilocode_change end
"update_todo_list",
"run_slash_command",
Expand Down
6 changes: 6 additions & 0 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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}']`
}
}

Expand Down Expand Up @@ -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
Expand Down
140 changes: 140 additions & 0 deletions src/core/kilocode/PlanMemoryManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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<string, string> = new Map()

constructor(taskId: string, globalStoragePath: string) {
this.taskId = taskId
this.globalStoragePath = globalStoragePath
}

/**
* Initialize the plan memory directory
*/
async initialize(): Promise<void> {
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<void> {
if (!this.planMemoryDir) return

try {
const entries = await fs.readdir(this.planMemoryDir, { withFileTypes: true })
for (const entry of entries) {
if (entry.isFile()) {
const filePath = path.join(this.planMemoryDir, entry.name)
const content = await fs.readFile(filePath, "utf-8")
if (!this.files.has(entry.name)) {
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<void> {
const safeFilename = path.basename(filename)
// Store in memory
this.files.set(safeFilename, content)

// Persist to disk
if (this.planMemoryDir) {
const filePath = path.join(this.planMemoryDir, safeFilename)
await fs.writeFile(filePath, content, "utf-8")
}
}

/**
* Read a plan file
*/
readFile(filename: string): string | undefined {
const safeFilename = path.basename(filename)
return this.files.get(safeFilename)
}

/**
* Check if a file exists
*/
hasFile(filename: string): boolean {
const safeFilename = path.basename(filename)
return this.files.has(safeFilename)
}

/**
* Get all plan files
*/
getAllFiles(): Map<string, string> {
return new Map(this.files)
}

/**
* Delete a plan file
*/
async deleteFile(filename: string): Promise<void> {
const safeFilename = path.basename(filename)
this.files.delete(safeFilename)

if (this.planMemoryDir) {
const filePath = path.join(this.planMemoryDir, safeFilename)
try {
await fs.unlink(filePath)
} catch (error) {
console.warn(`Failed to delete plan file ${safeFilename}:`, error)
}
}
}

/**
* Clear all plan files
*/
async clearAll(): Promise<void> {
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
}
}
4 changes: 3 additions & 1 deletion src/core/prompts/tools/native-tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,7 +31,8 @@ export const nativeTools = [
// insertContent,
listCodeDefinitionNames,
listFiles,
newTask,
// newTask,
planFileEdit,
read_file_multi,
runSlashCommand,
// searchAndReplace,
Expand Down
27 changes: 27 additions & 0 deletions src/core/prompts/tools/native-tools/plan_file_edit.ts
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -293,6 +294,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Task Bridge
enableBridge: boolean

// Plan Memory
planMemoryManager?: PlanMemoryManager

// Message Queue Service
public readonly messageQueueService: MessageQueueService
private messageQueueStateChangedHandler: (() => void) | undefined
Expand Down Expand Up @@ -441,6 +445,12 @@ export class Task extends EventEmitter<TaskEvents> 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
Expand Down
61 changes: 61 additions & 0 deletions src/core/tools/planFileEditTool.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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)
}
}
20 changes: 20 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Loading