Skip to content
Closed
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
53 changes: 53 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,59 @@ export class ClineProvider
}
}

async forkTaskFromMessage(messageTs: number) {
const currentTask = this.getCurrentTask()
if (!currentTask) {
throw new Error("No active task to fork from")
}

// Find the message index
const messageIndex = currentTask.clineMessages.findIndex((msg) => msg.ts === messageTs)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Bug: Using findIndex with timestamp comparison could fail if multiple messages share the same timestamp (e.g., messages created in rapid succession). Consider adding a fallback or using a more unique identifier.

// Consider adding a warning or fallback
if (messageIndex === -1) {
  // Check if this is due to timestamp precision issues
  const closeMatch = currentTask.clineMessages.findIndex(
    msg => Math.abs(msg.ts - messageTs) < 10
  )
  if (closeMatch !== -1) {
    console.warn(`Exact timestamp match not found, using closest match`)
    messageIndex = closeMatch
  } else {
    throw new Error("Message not found")
  }
}

if (messageIndex === -1) {
throw new Error("Message not found")
}

// Get messages up to and including the selected message
const messagesToCopy = currentTask.clineMessages.slice(0, messageIndex + 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation: Consider validating that the messages to copy contain at least one user message to ensure the forked conversation is meaningful:

const hasUserMessage = messagesToCopy.some(msg => msg.type === 'user')
if (!hasUserMessage) {
  throw new Error("Cannot fork from this point: no user messages found")
}


// Create a new task with the copied conversation
const newTaskId = `${Date.now()}`
const historyItem: HistoryItem = {
id: newTaskId,
ts: Date.now(),
task: messagesToCopy[0]?.text || "Forked conversation",
mode: currentTask.taskMode || defaultModeSlug,
workspace: this.cwd,
number: (this.getGlobalState("taskHistory") ?? []).length + 1,
tokensIn: 0,
tokensOut: 0,
totalCost: 0,
}

// Save the new task to history
await this.updateTaskHistory(historyItem)

// Create the task directory and save messages
const { getTaskDirectoryPath } = await import("../../utils/storage")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, newTaskId)

// Ensure directory exists
await fs.mkdir(taskDirPath, { recursive: true })
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error Handling: File system operations should be wrapped in try-catch to handle potential failures gracefully:

try {
  await fs.mkdir(taskDirPath, { recursive: true })
  await fs.writeFile(uiMessagesFilePath, JSON.stringify(messagesToCopy, null, 2))
} catch (error) {
  console.error(`Failed to save forked task: ${error}`)
  throw new Error(`Failed to create forked task: ${error.message}`)
}


// Save the messages to the new task
const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
await fs.writeFile(uiMessagesFilePath, JSON.stringify(messagesToCopy, null, 2))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Issue: The forked task is missing API conversation history preservation. While UI messages are saved, the corresponding apiConversationHistory should also be preserved to maintain full context when the forked task is resumed.

// Also save API conversation history
const apiHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
const apiMessages = currentTask.apiConversationHistory.slice(0, messageIndex + 1)
await fs.writeFile(apiHistoryFilePath, JSON.stringify(apiMessages, null, 2))


// Create and show the new forked task
await this.createTaskWithHistoryItem(historyItem)

// Show success message
vscode.window.showInformationMessage(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I18n: The success message is hardcoded in English. Consider using the i18n system for consistency:

vscode.window.showInformationMessage(
  t("fork.success_message")
)

"Task forked successfully. You can now continue the conversation from this point.",
)
}

async deleteTaskFromState(id: string) {
const taskHistory = this.getGlobalState("taskHistory") ?? []
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
Expand Down
Loading
Loading