Compare commits

...

1 Commits

Author SHA1 Message Date
celestial-vault 3504644c43 move saveClineMessagesAndUpdateHistory out to a separate state utilities file 2025-06-11 22:15:21 -07:00
2 changed files with 238 additions and 64 deletions
+172 -64
View File
@@ -114,6 +114,7 @@ import { processFilesIntoText } from "@integrations/misc/extract-text"
import { featureFlagsService } from "@services/posthog/feature-flags/FeatureFlagsService"
import { StreamingJsonReplacer, ChangeLocation } from "@core/assistant-message/diff-json"
import { isClaude4ModelFamily } from "@/utils/model-utils"
import { saveClineMessagesAndUpdateHistory } from "./message-state"
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
@@ -346,52 +347,28 @@ export class Task {
message.conversationHistoryIndex = this.apiConversationHistory.length - 1 // NOTE: this is the index of the last added message which is the user message, and once the clinemessages have been presented we update the apiconversationhistory with the completed assistant message. This means when resetting to a message, we need to +1 this index to get the correct assistant message that this tool use corresponds to
message.conversationHistoryDeletedRange = this.conversationHistoryDeletedRange
this.clineMessages.push(message)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
private async overwriteClineMessages(newMessages: ClineMessage[]) {
this.clineMessages = newMessages
await this.saveClineMessagesAndUpdateHistory()
}
private async saveClineMessagesAndUpdateHistory() {
try {
await saveClineMessages(this.getContext(), this.taskId, this.clineMessages)
// combined as they are in ChatView
const apiMetrics = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1))))
const taskMessage = this.clineMessages[0] // first message is always the task say
const lastRelevantMessage =
this.clineMessages[
findLastIndex(this.clineMessages, (m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"))
]
const taskDir = await ensureTaskDirectoryExists(this.getContext(), this.taskId)
let taskDirSize = 0
try {
// getFolderSize.loose silently ignores errors
// returns # of bytes, size/1000/1000 = MB
taskDirSize = await getFolderSize.loose(taskDir)
} catch (error) {
console.error("Failed to get task directory size:", taskDir, error)
}
await this.updateTaskHistory({
id: this.taskId,
ts: lastRelevantMessage.ts,
task: taskMessage.text ?? "",
tokensIn: apiMetrics.totalTokensIn,
tokensOut: apiMetrics.totalTokensOut,
cacheWrites: apiMetrics.totalCacheWrites,
cacheReads: apiMetrics.totalCacheReads,
totalCost: apiMetrics.totalCost,
size: taskDirSize,
shadowGitConfigWorkTree: await this.checkpointTracker?.getShadowGitConfigWorkTree(),
cwdOnTaskInitialization: cwd,
conversationHistoryDeletedRange: this.conversationHistoryDeletedRange,
isFavorited: this.taskIsFavorited,
})
} catch (error) {
console.error("Failed to save cline messages:", error)
}
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
async restoreCheckpoint(messageTs: number, restoreType: ClineCheckpointRestore, offset?: number) {
@@ -518,9 +495,15 @@ export class Task {
})
}
await this.saveClineMessagesAndUpdateHistory()
sendRelinquishControlEvent()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
this.cancelTask() // the task is already cancelled by the provider beforehand, but we need to re-init to get the updated messages
} else {
@@ -793,7 +776,15 @@ export class Task {
// lastMessage.ts = askTs
lastMessage.text = text
lastMessage.partial = false
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
// await this.postStateToWebview()
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage)
@@ -901,7 +892,15 @@ export class Task {
lastMessage.partial = false
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
// await this.postStateToWebview()
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage) // more performant than an entire postStateToWebview
@@ -950,8 +949,15 @@ export class Task {
const lastMessage = this.clineMessages.at(-1)
if (lastMessage?.partial && lastMessage.type === type && (lastMessage.ask === askOrSay || lastMessage.say === askOrSay)) {
this.clineMessages.pop()
await this.saveClineMessagesAndUpdateHistory()
await this.postStateToWebview()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
}
@@ -1235,7 +1241,15 @@ export class Task {
const lastCheckpointMessage = findLast(this.clineMessages, (m) => m.say === "checkpoint_created")
if (lastCheckpointMessage) {
lastCheckpointMessage.lastCheckpointHash = commitHash
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
}) // silently fails for now
@@ -1267,7 +1281,15 @@ export class Task {
)
if (lastCompletionResultMessage) {
lastCompletionResultMessage.lastCheckpointHash = commitHash
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
} else {
console.error("Checkpoint tracker does not exist and could not be initialized for attempt completion")
@@ -1707,7 +1729,15 @@ export class Task {
if (contextManagementMetadata.updatedConversationHistoryDeletedRange) {
this.conversationHistoryDeletedRange = contextManagementMetadata.conversationHistoryDeletedRange
await this.saveClineMessagesAndUpdateHistory() // saves task history item which we use to keep track of conversation history deleted range
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
) // saves task history item which we use to keep track of conversation history deleted range
}
let stream = this.api.createMessage(systemPrompt, contextManagementMetadata.truncatedConversationHistory)
@@ -1732,7 +1762,15 @@ export class Task {
this.conversationHistoryDeletedRange,
"quarter", // Force aggressive truncation
)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
Date.now(),
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
@@ -1746,7 +1784,15 @@ export class Task {
this.conversationHistoryDeletedRange,
"quarter", // Force aggressive truncation
)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
Date.now(),
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
@@ -3510,8 +3556,15 @@ export class Task {
...sharedMessage,
selected: text,
} satisfies ClineAskQuestion)
await this.saveClineMessagesAndUpdateHistory()
telemetryService.captureOptionSelected(this.taskId, options.length, "act")
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
} else {
// Option not selected, send user feedback
@@ -3643,7 +3696,15 @@ export class Task {
this.conversationHistoryDeletedRange,
keepStrategy,
)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
Date.now(),
await ensureTaskDirectoryExists(this.getContext(), this.taskId),
@@ -3944,8 +4005,15 @@ export class Task {
...sharedMessage,
selected: text,
} satisfies ClinePlanModeResponse)
await this.saveClineMessagesAndUpdateHistory()
telemetryService.captureOptionSelected(this.taskId, options.length, "plan")
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
} else {
// Option not selected, send user feedback
@@ -4047,7 +4115,15 @@ export class Task {
) {
lastCompletionResultMessage.text += COMPLETION_RESULT_CHANGES_FLAG
}
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
}
try {
@@ -4382,7 +4458,15 @@ export class Task {
this.clineMessages[lastApiReqIndex].text = JSON.stringify({
request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
} satisfies ClineApiReqInfo)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.postStateToWebview()
try {
@@ -4453,7 +4537,15 @@ export class Task {
// update api_req_started to have cancelled and cost, so that we can display the cost of the partial stream
updateApiReqMsg(cancelReason, streamingFailedMessage)
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
telemetryService.captureConversationTurnEvent(
this.taskId,
@@ -4579,7 +4671,15 @@ export class Task {
totalCost = apiStreamUsage.totalCost
}
updateApiReqMsg()
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.postStateToWebview()
})
}
@@ -4603,7 +4703,15 @@ export class Task {
}
updateApiReqMsg()
await this.saveClineMessagesAndUpdateHistory()
await saveClineMessagesAndUpdateHistory(
this.getContext(),
this.taskId,
this.clineMessages,
this.taskIsFavorited ?? false,
this.conversationHistoryDeletedRange,
this.checkpointTracker,
this.updateTaskHistory,
)
await this.postStateToWebview()
// now add to apiconversationhistory
+66
View File
@@ -0,0 +1,66 @@
import { combineApiRequests } from "@/shared/combineApiRequests"
import { ensureTaskDirectoryExists, saveClineMessages } from "../storage/disk"
import * as vscode from "vscode"
import { ClineMessage } from "@/shared/ExtensionMessage"
import { getApiMetrics } from "@/shared/getApiMetrics"
import { combineCommandSequences } from "@/shared/combineCommandSequences"
import { findLastIndex } from "@/shared/array"
import getFolderSize from "get-folder-size"
import os from "os"
import * as path from "path"
import CheckpointTracker from "@integrations/checkpoints/CheckpointTracker"
import { HistoryItem } from "@/shared/HistoryItem"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
// need to call getContext() from the task object when passing in context
export async function saveClineMessagesAndUpdateHistory(
context: vscode.ExtensionContext,
taskId: string,
clineMessages: ClineMessage[],
taskIsFavorited: boolean,
conversationHistoryDeletedRange: [number, number] | undefined,
checkpointTracker: CheckpointTracker | undefined,
updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>,
) {
try {
await saveClineMessages(context, taskId, clineMessages)
// combined as they are in ChatView
const apiMetrics = getApiMetrics(combineApiRequests(combineCommandSequences(clineMessages.slice(1))))
const taskMessage = clineMessages[0] // first message is always the task say
const lastRelevantMessage =
clineMessages[
findLastIndex(
clineMessages,
(message) => !(message.ask === "resume_task" || message.ask === "resume_completed_task"),
)
]
const taskDir = await ensureTaskDirectoryExists(context, taskId)
let taskDirSize = 0
try {
// getFolderSize.loose silently ignores errors
// returns # of bytes, size/1000/1000 = MB
taskDirSize = await getFolderSize.loose(taskDir)
} catch (error) {
console.error("Failed to get task directory size:", taskDir, error)
}
await updateTaskHistory({
id: taskId,
ts: lastRelevantMessage.ts,
task: taskMessage.text ?? "",
tokensIn: apiMetrics.totalTokensIn,
tokensOut: apiMetrics.totalTokensOut,
cacheWrites: apiMetrics.totalCacheWrites,
cacheReads: apiMetrics.totalCacheReads,
totalCost: apiMetrics.totalCost,
size: taskDirSize,
shadowGitConfigWorkTree: await checkpointTracker?.getShadowGitConfigWorkTree(),
cwdOnTaskInitialization: cwd,
conversationHistoryDeletedRange: conversationHistoryDeletedRange,
isFavorited: taskIsFavorited,
})
} catch (error) {
console.error("Failed to save cline messages:", error)
}
}