mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 21:01:06 -05:00
Add task history
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
import { ClaudeDev } from "../ClaudeDev"
|
||||
import { ApiModelId, ApiProvider } from "../shared/api"
|
||||
import { ExtensionMessage } from "../shared/ExtensionMessage"
|
||||
import { WebviewMessage } from "../shared/WebviewMessage"
|
||||
import { downloadTask, getNonce, getUri, selectImages } from "../utils"
|
||||
import * as path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { HistoryItem } from "../shared/HistoryItem"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
|
||||
@@ -18,6 +23,7 @@ type GlobalStateKey =
|
||||
| "maxRequestsPerTask"
|
||||
| "lastShownAnnouncementId"
|
||||
| "customInstructions"
|
||||
| "taskHistory"
|
||||
|
||||
export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
||||
@@ -27,10 +33,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
private claudeDev?: ClaudeDev
|
||||
private latestAnnouncementId = "aug-15-2024" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
private readonly outputChannel: vscode.OutputChannel
|
||||
) {
|
||||
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
|
||||
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
|
||||
}
|
||||
|
||||
@@ -142,6 +145,20 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
this.claudeDev = new ClaudeDev(this, apiConfiguration, maxRequestsPerTask, customInstructions, task, images)
|
||||
}
|
||||
|
||||
async initClaudeDevWithHistoryItem(historyItem: HistoryItem) {
|
||||
await this.clearTask()
|
||||
const { maxRequestsPerTask, apiConfiguration, customInstructions } = await this.getState()
|
||||
this.claudeDev = new ClaudeDev(
|
||||
this,
|
||||
apiConfiguration,
|
||||
maxRequestsPerTask,
|
||||
customInstructions,
|
||||
undefined,
|
||||
undefined,
|
||||
historyItem
|
||||
)
|
||||
}
|
||||
|
||||
// Send any JSON serializable data to the react app
|
||||
async postMessageToWebview(message: ExtensionMessage) {
|
||||
await this.view?.webview.postMessage(message)
|
||||
@@ -304,13 +321,23 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("lastShownAnnouncementId", this.latestAnnouncementId)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "downloadTask":
|
||||
downloadTask(this.claudeDev?.apiConversationHistory ?? [])
|
||||
break
|
||||
case "selectImages":
|
||||
const images = await selectImages()
|
||||
await this.postMessageToWebview({ type: "selectedImages", images })
|
||||
break
|
||||
case "exportCurrentTask":
|
||||
const firstMessageTs = this.claudeDev?.claudeMessages.at(0)?.ts ?? Date.now()
|
||||
downloadTask(firstMessageTs, this.claudeDev?.apiConversationHistory ?? [])
|
||||
break
|
||||
case "showTaskWithId":
|
||||
this.showTaskWithId(message.text!)
|
||||
break
|
||||
case "deleteTaskWithId":
|
||||
this.deleteTaskWithId(message.text!)
|
||||
break
|
||||
case "exportTaskWithId":
|
||||
this.exportTaskWithId(message.text!)
|
||||
break
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -320,8 +347,94 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
}
|
||||
|
||||
// Task history
|
||||
|
||||
async getTaskWithId(id: string): Promise<{
|
||||
historyItem: HistoryItem
|
||||
taskDirPath: string
|
||||
apiConversationHistoryFilePath: string
|
||||
claudeMessagesFilePath: string
|
||||
apiConversationHistory: Anthropic.MessageParam[]
|
||||
}> {
|
||||
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
|
||||
const historyItem = history.find((item) => item.id === id)
|
||||
if (historyItem) {
|
||||
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
|
||||
const apiConversationHistoryFilePath = path.join(taskDirPath, "api_conversation_history.json")
|
||||
const claudeMessagesFilePath = path.join(taskDirPath, "claude_messages.json")
|
||||
const fileExists = await fs
|
||||
.access(apiConversationHistoryFilePath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (fileExists) {
|
||||
const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8"))
|
||||
return {
|
||||
historyItem,
|
||||
taskDirPath,
|
||||
apiConversationHistoryFilePath,
|
||||
claudeMessagesFilePath,
|
||||
apiConversationHistory,
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we tried to get a task that doesn't exist, remove it from state
|
||||
await this.deleteTaskFromState(id)
|
||||
throw new Error("Task not found")
|
||||
}
|
||||
|
||||
async showTaskWithId(id: string) {
|
||||
if (id !== this.claudeDev?.taskId) {
|
||||
// non-current task
|
||||
const { historyItem } = await this.getTaskWithId(id)
|
||||
await this.initClaudeDevWithHistoryItem(historyItem) // clears existing task
|
||||
}
|
||||
await this.postMessageToWebview({ type: "action", action: "chatButtonTapped" })
|
||||
}
|
||||
|
||||
async exportTaskWithId(id: string) {
|
||||
const { historyItem, apiConversationHistory } = await this.getTaskWithId(id)
|
||||
await downloadTask(historyItem.ts, apiConversationHistory)
|
||||
}
|
||||
|
||||
async deleteTaskWithId(id: string) {
|
||||
if (id === this.claudeDev?.taskId) {
|
||||
await this.clearTask()
|
||||
}
|
||||
|
||||
const { taskDirPath, apiConversationHistoryFilePath, claudeMessagesFilePath } = await this.getTaskWithId(id)
|
||||
|
||||
// Delete the task files
|
||||
const apiConversationHistoryFileExists = await fs
|
||||
.access(apiConversationHistoryFilePath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (apiConversationHistoryFileExists) {
|
||||
await fs.unlink(apiConversationHistoryFilePath)
|
||||
}
|
||||
const claudeMessagesFileExists = await fs
|
||||
.access(claudeMessagesFilePath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (claudeMessagesFileExists) {
|
||||
await fs.unlink(claudeMessagesFilePath)
|
||||
}
|
||||
await fs.rmdir(taskDirPath) // succeeds if the dir is empty
|
||||
|
||||
await this.deleteTaskFromState(id)
|
||||
}
|
||||
|
||||
async deleteTaskFromState(id: string) {
|
||||
// Remove the task from history
|
||||
const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
|
||||
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
|
||||
await this.updateGlobalState("taskHistory", updatedTaskHistory)
|
||||
|
||||
// Notify the webview that the task has been deleted
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
async postStateToWebview() {
|
||||
const { apiConfiguration, maxRequestsPerTask, lastShownAnnouncementId, customInstructions } =
|
||||
const { apiConfiguration, maxRequestsPerTask, lastShownAnnouncementId, customInstructions, taskHistory } =
|
||||
await this.getState()
|
||||
this.postMessageToWebview({
|
||||
type: "state",
|
||||
@@ -332,6 +445,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
customInstructions,
|
||||
themeName: vscode.workspace.getConfiguration("workbench").get<string>("colorTheme"),
|
||||
claudeMessages: this.claudeDev?.claudeMessages || [],
|
||||
taskHistory: (taskHistory || []).sort((a, b) => b.ts - a.ts),
|
||||
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
|
||||
},
|
||||
})
|
||||
@@ -435,6 +549,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
maxRequestsPerTask,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<ApiModelId | undefined>,
|
||||
@@ -446,6 +561,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
this.getGlobalState("maxRequestsPerTask") as Promise<number | undefined>,
|
||||
this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
|
||||
this.getGlobalState("customInstructions") as Promise<string | undefined>,
|
||||
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -475,9 +591,22 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
maxRequestsPerTask,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
}
|
||||
}
|
||||
|
||||
async updateTaskHistory(item: HistoryItem): Promise<HistoryItem[]> {
|
||||
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
|
||||
const existingItemIndex = history.findIndex((h) => h.id === item.id)
|
||||
if (existingItemIndex !== -1) {
|
||||
history[existingItemIndex] = item
|
||||
} else {
|
||||
history.push(item)
|
||||
}
|
||||
await this.updateGlobalState("taskHistory", history)
|
||||
return history
|
||||
}
|
||||
|
||||
// global
|
||||
|
||||
private async updateGlobalState(key: GlobalStateKey, value: any) {
|
||||
|
||||
Reference in New Issue
Block a user