Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 8 additions & 4 deletions src/core/checkpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,14 @@ export function getCheckpointService(cline: Task) {
try {
provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: to })

cline.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((err) => {
log("[Cline#getCheckpointService] caught unexpected error in say('checkpoint_saved')")
console.error(err)
})
cline
.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }, undefined, {
isNonInteractive: true,
})
.catch((err) => {
log("[Cline#getCheckpointService] caught unexpected error in say('checkpoint_saved')")
console.error(err)
})
} catch (err) {
log("[Cline#getCheckpointService] caught unexpected error in on('checkpoint'), disabling checkpoints")
console.error(err)
Expand Down
47 changes: 30 additions & 17 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,25 +492,30 @@ export class Task extends EventEmitter<ClineEvents> {
partial?: boolean,
checkpoint?: Record<string, unknown>,
progressStatus?: ToolProgressStatus,
options: {
isNonInteractive?: boolean
} = {},
): Promise<undefined> {
if (this.abort) {
throw new Error(`[Cline#say] task ${this.taskId}.${this.instanceId} aborted`)
}

if (partial !== undefined) {
const lastMessage = this.clineMessages.at(-1)

const isUpdatingPreviousPartial =
lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type

if (partial) {
if (isUpdatingPreviousPartial) {
// existing partial message, so update it
// Existing partial message, so update it.
lastMessage.text = text
lastMessage.images = images
lastMessage.partial = partial
lastMessage.progressStatus = progressStatus
this.updateClineMessage(lastMessage)
} else {
// this is a new partial message, so add it with partial state
// This is a new partial message, so add it with partial state.
const sayTs = Date.now()
this.lastMessageTs = sayTs
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, partial })
Expand All @@ -521,15 +526,14 @@ export class Task extends EventEmitter<ClineEvents> {
// This is the complete version of a previously partial
// message, so replace the partial with the complete version.
this.lastMessageTs = lastMessage.ts
Copy link
Collaborator

Choose a reason for hiding this comment

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

And this?

// lastMessage.ts = sayTs
lastMessage.text = text
lastMessage.images = images
lastMessage.partial = false
lastMessage.progressStatus = progressStatus
// Instead of streaming partialMessage events, we do a save
// and post like normal to persist to disk.
await this.saveClineMessages()
// More performant than an entire postStateToWebview.
// More performant than an entire `postStateToWebview`.
this.updateClineMessage(lastMessage)
} else {
// This is a new and complete message, so add it like normal.
Expand All @@ -539,9 +543,17 @@ export class Task extends EventEmitter<ClineEvents> {
}
}
} else {
// this is a new non-partial message, so add it like normal
// This is a new non-partial message, so add it like normal.
const sayTs = Date.now()
this.lastMessageTs = sayTs

// A "non-interactive" message is a message is one that the user
// does not need to respond to. We don't want these message types
// to trigger an update to `lastMessageTs` since they can be created
// asynchronously and could interrupt a pending ask.
if (!options.isNonInteractive) {
this.lastMessageTs = sayTs
}

await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint })
}
}
Expand All @@ -559,8 +571,12 @@ export class Task extends EventEmitter<ClineEvents> {
// Start / Abort / Resume

private async startTask(task?: string, images?: string[]): Promise<void> {
// conversationHistory (for API) and clineMessages (for webview) need to be in sync
// if the extension process were killed, then on restart the clineMessages might not be empty, so we need to set it to [] when we create a new Cline client (otherwise webview would show stale messages from previous session)
// `conversationHistory` (for API) and `clineMessages` (for webview)
// need to be in sync.
// If the extension process were killed, then on restart the
// `clineMessages` might not be empty, so we need to set it to [] when
// we create a new Cline client (otherwise webview would show stale
// messages from previous session).
this.clineMessages = []
this.apiConversationHistory = []
await this.providerRef.deref()?.postStateToWebview()
Expand All @@ -582,28 +598,25 @@ export class Task extends EventEmitter<ClineEvents> {
}

public async resumePausedTask(lastMessage: string) {
// release this Cline instance from paused state
// Release this Cline instance from paused state.
this.isPaused = false
this.emit("taskUnpaused")

// fake an answer from the subtask that it has completed running and this is the result of what it has done
// add the message to the chat history and to the webview ui
// Fake an answer from the subtask that it has completed running and
// this is the result of what it has done add the message to the chat
// history and to the webview ui.
try {
await this.say("subtask_result", lastMessage)

await this.addToApiConversationHistory({
role: "user",
content: [
{
type: "text",
text: `[new_task completed] Result: ${lastMessage}`,
},
],
content: [{ type: "text", text: `[new_task completed] Result: ${lastMessage}` }],
})
} catch (error) {
this.providerRef
.deref()
?.log(`Error failed to add reply from subtast into conversation of parent task, error: ${error}`)

throw error
}
}
Expand Down