Skip to content

Commit 00cf39e

Browse files
zxdvdcte
authored andcommitted
fix(task): temporary fix for the ask error (#3471)
Co-authored-by: cte <[email protected]>
1 parent 4871fb7 commit 00cf39e

File tree

10 files changed

+81
-69
lines changed

10 files changed

+81
-69
lines changed

src/core/checkpoints/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ export function getCheckpointService(cline: Task) {
9393
try {
9494
provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: to })
9595

96-
cline.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((err) => {
97-
log("[Cline#getCheckpointService] caught unexpected error in say('checkpoint_saved')")
98-
console.error(err)
99-
})
96+
cline
97+
.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }, undefined, {
98+
isNonInteractive: true,
99+
})
100+
.catch((err) => {
101+
log("[Cline#getCheckpointService] caught unexpected error in say('checkpoint_saved')")
102+
console.error(err)
103+
})
100104
} catch (err) {
101105
log("[Cline#getCheckpointService] caught unexpected error in on('checkpoint'), disabling checkpoints")
102106
console.error(err)

src/core/task/Task.ts

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class Task extends EventEmitter<ClineEvents> {
164164
consecutiveMistakeCount: number = 0
165165
consecutiveMistakeLimit: number
166166
consecutiveMistakeCountForApplyDiff: Map<string, number> = new Map()
167-
private toolUsage: ToolUsage = {}
167+
toolUsage: ToolUsage = {}
168168

169169
// Checkpoints
170170
enableCheckpoints: boolean
@@ -488,56 +488,81 @@ export class Task extends EventEmitter<ClineEvents> {
488488
partial?: boolean,
489489
checkpoint?: Record<string, unknown>,
490490
progressStatus?: ToolProgressStatus,
491+
options: {
492+
isNonInteractive?: boolean
493+
} = {},
491494
): Promise<undefined> {
492495
if (this.abort) {
493496
throw new Error(`[Cline#say] task ${this.taskId}.${this.instanceId} aborted`)
494497
}
495498

496499
if (partial !== undefined) {
497500
const lastMessage = this.clineMessages.at(-1)
501+
498502
const isUpdatingPreviousPartial =
499503
lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type
504+
500505
if (partial) {
501506
if (isUpdatingPreviousPartial) {
502-
// existing partial message, so update it
507+
// Existing partial message, so update it.
503508
lastMessage.text = text
504509
lastMessage.images = images
505510
lastMessage.partial = partial
506511
lastMessage.progressStatus = progressStatus
507512
this.updateClineMessage(lastMessage)
508513
} else {
509-
// this is a new partial message, so add it with partial state
514+
// This is a new partial message, so add it with partial state.
510515
const sayTs = Date.now()
511-
this.lastMessageTs = sayTs
516+
517+
if (!options.isNonInteractive) {
518+
this.lastMessageTs = sayTs
519+
}
520+
512521
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, partial })
513522
}
514523
} else {
515524
// New now have a complete version of a previously partial message.
525+
// This is the complete version of a previously partial
526+
// message, so replace the partial with the complete version.
516527
if (isUpdatingPreviousPartial) {
517-
// This is the complete version of a previously partial
518-
// message, so replace the partial with the complete version.
519-
this.lastMessageTs = lastMessage.ts
520-
// lastMessage.ts = sayTs
528+
if (!options.isNonInteractive) {
529+
this.lastMessageTs = lastMessage.ts
530+
}
531+
521532
lastMessage.text = text
522533
lastMessage.images = images
523534
lastMessage.partial = false
524535
lastMessage.progressStatus = progressStatus
536+
525537
// Instead of streaming partialMessage events, we do a save
526538
// and post like normal to persist to disk.
527539
await this.saveClineMessages()
528-
// More performant than an entire postStateToWebview.
540+
541+
// More performant than an entire `postStateToWebview`.
529542
this.updateClineMessage(lastMessage)
530543
} else {
531544
// This is a new and complete message, so add it like normal.
532545
const sayTs = Date.now()
533-
this.lastMessageTs = sayTs
546+
547+
if (!options.isNonInteractive) {
548+
this.lastMessageTs = sayTs
549+
}
550+
534551
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images })
535552
}
536553
}
537554
} else {
538-
// this is a new non-partial message, so add it like normal
555+
// This is a new non-partial message, so add it like normal.
539556
const sayTs = Date.now()
540-
this.lastMessageTs = sayTs
557+
558+
// A "non-interactive" message is a message is one that the user
559+
// does not need to respond to. We don't want these message types
560+
// to trigger an update to `lastMessageTs` since they can be created
561+
// asynchronously and could interrupt a pending ask.
562+
if (!options.isNonInteractive) {
563+
this.lastMessageTs = sayTs
564+
}
565+
541566
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint })
542567
}
543568
}
@@ -555,8 +580,12 @@ export class Task extends EventEmitter<ClineEvents> {
555580
// Start / Abort / Resume
556581

557582
private async startTask(task?: string, images?: string[]): Promise<void> {
558-
// conversationHistory (for API) and clineMessages (for webview) need to be in sync
559-
// 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)
583+
// `conversationHistory` (for API) and `clineMessages` (for webview)
584+
// need to be in sync.
585+
// If the extension process were killed, then on restart the
586+
// `clineMessages` might not be empty, so we need to set it to [] when
587+
// we create a new Cline client (otherwise webview would show stale
588+
// messages from previous session).
560589
this.clineMessages = []
561590
this.apiConversationHistory = []
562591
await this.providerRef.deref()?.postStateToWebview()
@@ -578,28 +607,25 @@ export class Task extends EventEmitter<ClineEvents> {
578607
}
579608

580609
public async resumePausedTask(lastMessage: string) {
581-
// release this Cline instance from paused state
610+
// Release this Cline instance from paused state.
582611
this.isPaused = false
583612
this.emit("taskUnpaused")
584613

585-
// fake an answer from the subtask that it has completed running and this is the result of what it has done
586-
// add the message to the chat history and to the webview ui
614+
// Fake an answer from the subtask that it has completed running and
615+
// this is the result of what it has done add the message to the chat
616+
// history and to the webview ui.
587617
try {
588618
await this.say("subtask_result", lastMessage)
589619

590620
await this.addToApiConversationHistory({
591621
role: "user",
592-
content: [
593-
{
594-
type: "text",
595-
text: `[new_task completed] Result: ${lastMessage}`,
596-
},
597-
],
622+
content: [{ type: "text", text: `[new_task completed] Result: ${lastMessage}` }],
598623
})
599624
} catch (error) {
600625
this.providerRef
601626
.deref()
602627
?.log(`Error failed to add reply from subtast into conversation of parent task, error: ${error}`)
628+
603629
throw error
604630
}
605631
}
@@ -1629,17 +1655,9 @@ export class Task extends EventEmitter<ClineEvents> {
16291655
}
16301656
}
16311657

1632-
public getToolUsage() {
1633-
return this.toolUsage
1634-
}
1635-
16361658
// Getters
16371659

16381660
public get cwd() {
16391661
return this.workspacePath
16401662
}
1641-
1642-
public getFileContextTracker(): FileContextTracker {
1643-
return this.fileContextTracker
1644-
}
16451663
}

src/core/tools/__tests__/readFileTool.test.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ let mockInputContent = ""
3333

3434
jest.mock("../../../integrations/misc/extract-text", () => {
3535
const actual = jest.requireActual("../../../integrations/misc/extract-text")
36-
// Create a spy on the actual addLineNumbers function
36+
// Create a spy on the actual addLineNumbers function.
3737
const addLineNumbersSpy = jest.spyOn(actual, "addLineNumbers")
3838

3939
return {
4040
...actual,
41-
// Expose the spy so tests can access it
41+
// Expose the spy so tests can access it.
4242
__addLineNumbersSpy: addLineNumbersSpy,
4343
extractTextFromFile: jest.fn().mockImplementation((_filePath) => {
44-
// Use the actual addLineNumbers function
44+
// Use the actual addLineNumbers function.
4545
const content = mockInputContent
4646
return Promise.resolve(actual.addLineNumbers(content))
4747
}),
@@ -87,39 +87,33 @@ describe("read_file tool with maxReadFileLine setting", () => {
8787
const mockedIsBinaryFile = isBinaryFile as jest.MockedFunction<typeof isBinaryFile>
8888
const mockedPathResolve = path.resolve as jest.MockedFunction<typeof path.resolve>
8989

90-
// Mock instances
9190
const mockCline: any = {}
9291
let mockProvider: any
9392
let toolResult: ToolResponse | undefined
9493

9594
beforeEach(() => {
9695
jest.clearAllMocks()
9796

98-
// Setup path resolution
9997
mockedPathResolve.mockReturnValue(absoluteFilePath)
100-
101-
// Setup mocks for file operations
10298
mockedIsBinaryFile.mockResolvedValue(false)
10399

104-
// Set the default content for the mock
105100
mockInputContent = fileContent
106101

107-
// Setup the extractTextFromFile mock implementation with the current mockInputContent
102+
// Setup the extractTextFromFile mock implementation with the current
103+
// mockInputContent.
108104
mockedExtractTextFromFile.mockImplementation((_filePath) => {
109105
const actual = jest.requireActual("../../../integrations/misc/extract-text")
110106
return Promise.resolve(actual.addLineNumbers(mockInputContent))
111107
})
112108

113109
// No need to setup the extractTextFromFile mock implementation here
114-
// as it's already defined at the module level
110+
// as it's already defined at the module level.
115111

116-
// Setup mock provider
117112
mockProvider = {
118113
getState: jest.fn(),
119114
deref: jest.fn().mockReturnThis(),
120115
}
121116

122-
// Setup Cline instance with mock methods
123117
mockCline.cwd = "/"
124118
mockCline.task = "Test"
125119
mockCline.providerRef = mockProvider
@@ -129,12 +123,14 @@ describe("read_file tool with maxReadFileLine setting", () => {
129123
mockCline.say = jest.fn().mockResolvedValue(undefined)
130124
mockCline.ask = jest.fn().mockResolvedValue(true)
131125
mockCline.presentAssistantMessage = jest.fn()
132-
mockCline.getFileContextTracker = jest.fn().mockReturnValue({
126+
127+
mockCline.fileContextTracker = {
133128
trackFileContext: jest.fn().mockResolvedValue(undefined),
134-
})
129+
}
130+
135131
mockCline.recordToolUsage = jest.fn().mockReturnValue(undefined)
136132
mockCline.recordToolError = jest.fn().mockReturnValue(undefined)
137-
// Reset tool result
133+
138134
toolResult = undefined
139135
})
140136

@@ -433,22 +429,16 @@ describe("read_file tool XML output structure", () => {
433429
beforeEach(() => {
434430
jest.clearAllMocks()
435431

436-
// Setup path resolution
437432
mockedPathResolve.mockReturnValue(absoluteFilePath)
438-
439-
// Setup mocks for file operations
440433
mockedIsBinaryFile.mockResolvedValue(false)
441434

442-
// Set the default content for the mock
443435
mockInputContent = fileContent
444436

445-
// Setup mock provider
446437
mockProvider = {
447438
getState: jest.fn().mockResolvedValue({ maxReadFileLine: 500 }),
448439
deref: jest.fn().mockReturnThis(),
449440
}
450441

451-
// Setup Cline instance with mock methods
452442
mockCline.cwd = "/"
453443
mockCline.task = "Test"
454444
mockCline.providerRef = mockProvider
@@ -459,14 +449,14 @@ describe("read_file tool XML output structure", () => {
459449
mockCline.ask = jest.fn().mockResolvedValue(true)
460450
mockCline.presentAssistantMessage = jest.fn()
461451
mockCline.sayAndCreateMissingParamError = jest.fn().mockResolvedValue("Missing required parameter")
462-
// Add mock for getFileContextTracker method
463-
mockCline.getFileContextTracker = jest.fn().mockReturnValue({
452+
453+
mockCline.fileContextTracker = {
464454
trackFileContext: jest.fn().mockResolvedValue(undefined),
465-
})
455+
}
456+
466457
mockCline.recordToolUsage = jest.fn().mockReturnValue(undefined)
467458
mockCline.recordToolError = jest.fn().mockReturnValue(undefined)
468459

469-
// Reset tool result
470460
toolResult = undefined
471461
})
472462

src/core/tools/applyDiffTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export async function applyDiffTool(
164164

165165
// Track file edit operation
166166
if (relPath) {
167-
await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource)
167+
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
168168
}
169169

170170
// Used to determine if we should wait for busy terminal to update before sending api request

src/core/tools/attemptCompletionTool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function attemptCompletionTool(
4646
await cline.say("completion_result", removeClosingTag("result", result), undefined, false)
4747

4848
telemetryService.captureTaskCompleted(cline.taskId)
49-
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage())
49+
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
5050

5151
await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {})
5252
}
@@ -72,7 +72,7 @@ export async function attemptCompletionTool(
7272
// Haven't sent a command message yet so first send completion_result then command.
7373
await cline.say("completion_result", result, undefined, false)
7474
telemetryService.captureTaskCompleted(cline.taskId)
75-
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage())
75+
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
7676
}
7777

7878
// Complete command message.
@@ -97,7 +97,7 @@ export async function attemptCompletionTool(
9797
} else {
9898
await cline.say("completion_result", result, undefined, false)
9999
telemetryService.captureTaskCompleted(cline.taskId)
100-
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage())
100+
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
101101
}
102102

103103
if (cline.parentTask) {

src/core/tools/insertContentTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export async function insertContentTool(
132132

133133
// Track file edit operation
134134
if (relPath) {
135-
await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource)
135+
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
136136
}
137137

138138
cline.didEditFile = true

src/core/tools/listCodeDefinitionNamesTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export async function listCodeDefinitionNamesTool(
6464
}
6565

6666
if (relPath) {
67-
await cline.getFileContextTracker().trackFileContext(relPath, "read_tool" as RecordSource)
67+
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
6868
}
6969

7070
pushToolResult(result)

src/core/tools/readFileTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export async function readFileTool(
245245

246246
// Track file read operation
247247
if (relPath) {
248-
await cline.getFileContextTracker().trackFileContext(relPath, "read_tool" as RecordSource)
248+
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
249249
}
250250

251251
// Format the result into the required XML structure

src/core/tools/searchAndReplaceTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export async function searchAndReplaceTool(
215215

216216
// Track file edit operation
217217
if (relPath) {
218-
await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource)
218+
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
219219
}
220220

221221
cline.didEditFile = true

src/core/tools/writeToFileTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export async function writeToFileTool(
211211

212212
// Track file edit operation
213213
if (relPath) {
214-
await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource)
214+
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
215215
}
216216

217217
cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request

0 commit comments

Comments
 (0)