Skip to content

Commit a35c495

Browse files
Revert "Fix: Orphaned Partial Ask Messages Bug"
Revert "Fix: Orphaned Partial Ask Messages Bug"
2 parents ab41eb8 + 94552b8 commit a35c495

File tree

6 files changed

+112
-188
lines changed

6 files changed

+112
-188
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
Revert orphaned partial ask messages fix

cli/src/state/atoms/__tests__/partial-race-condition.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,50 @@ describe("Partial Race Condition Bug Fix", () => {
9898
expect(messages[0]?.text).toBe("git status")
9999
})
100100

101+
it("should auto-complete orphaned partial ask when subsequent messages arrive (CLI workaround)", () => {
102+
// Simulate the extension bug: partial ask message followed by other messages
103+
// without ever sending a partial=false update
104+
const stateWithOrphanedPartial: ExtensionState = {
105+
version: "1.0.0",
106+
apiConfiguration: {},
107+
chatMessages: [
108+
{
109+
ts: 1000,
110+
type: "ask",
111+
ask: "command",
112+
text: "git status",
113+
partial: true, // Orphaned - never completed by extension
114+
},
115+
{
116+
ts: 2000,
117+
type: "say",
118+
say: "checkpoint_saved",
119+
text: "abc123",
120+
},
121+
{
122+
ts: 3000,
123+
type: "say",
124+
say: "text",
125+
text: "Command executed",
126+
},
127+
],
128+
mode: "code",
129+
customModes: [],
130+
taskHistoryFullLength: 0,
131+
taskHistoryVersion: 0,
132+
renderContext: "cli",
133+
telemetrySetting: "disabled",
134+
}
135+
136+
store.set(updateExtensionStateAtom, stateWithOrphanedPartial)
137+
138+
// CLI should auto-complete the orphaned partial ask
139+
const messages = store.get(chatMessagesAtom)
140+
expect(messages).toHaveLength(3)
141+
expect(messages[0]?.partial).toBe(false) // Auto-completed!
142+
expect(messages[0]?.ask).toBe("command")
143+
})
144+
101145
it("should handle the race condition for any ask message type", () => {
102146
// Test with followup message
103147
const initialState: ExtensionState = {

cli/src/state/atoms/extension.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ export const updateExtensionStateAtom = atom(null, (get, set, state: ExtensionSt
197197
const incomingMessages = state.clineMessages || state.chatMessages || []
198198

199199
// Reconcile with current messages to preserve streaming state
200-
const reconciledMessages = reconcileMessages(currentMessages, incomingMessages, versionMap, streamingSet)
200+
let reconciledMessages = reconcileMessages(currentMessages, incomingMessages, versionMap, streamingSet)
201+
202+
// Auto-complete orphaned partial ask messages (CLI-only workaround for extension bug)
203+
reconciledMessages = autoCompleteOrphanedPartialAsks(reconciledMessages)
201204

202205
set(chatMessagesAtom, reconciledMessages)
203206

@@ -456,6 +459,57 @@ function getMessageContentLength(msg: ExtensionChatMessage): number {
456459
return length
457460
}
458461

462+
/**
463+
* Auto-complete orphaned partial ask messages (CLI-only workaround)
464+
*
465+
* This handles the extension bug where ask messages can get stuck with partial=true
466+
* when other messages (like checkpoint_saved) are added between the partial message
467+
* and its completion, causing the extension to create a new message instead of updating.
468+
*
469+
* Detection logic:
470+
* - If an ask message has partial=true
471+
* - AND there's a subsequent message with a later timestamp
472+
* - AND that subsequent message is NOT command_output (which is expected during command execution)
473+
* - THEN mark the partial ask as complete (partial=false)
474+
*
475+
* This ensures messages don't get stuck in partial state indefinitely.
476+
*/
477+
function autoCompleteOrphanedPartialAsks(messages: ExtensionChatMessage[]): ExtensionChatMessage[] {
478+
const result = [...messages]
479+
480+
for (let i = 0; i < result.length; i++) {
481+
const msg = result[i]
482+
483+
// Only process partial ask messages
484+
if (!msg || msg.type !== "ask" || !msg.partial) {
485+
continue
486+
}
487+
488+
// Check if there's a subsequent message (not command_output)
489+
let hasSubsequentMessage = false
490+
for (let j = i + 1; j < result.length; j++) {
491+
const nextMsg = result[j]
492+
if (!nextMsg) continue
493+
494+
// Skip command_output messages as they're expected during command execution
495+
if (nextMsg.ask === "command_output") {
496+
continue
497+
}
498+
499+
// Found a subsequent non-command_output message
500+
hasSubsequentMessage = true
501+
break
502+
}
503+
504+
// If there's a subsequent message, this partial ask is orphaned - mark it complete
505+
if (hasSubsequentMessage) {
506+
result[i] = { ...msg, partial: false }
507+
}
508+
}
509+
510+
return result
511+
}
512+
459513
/**
460514
* Helper function to reconcile messages from state updates with existing messages
461515
* Strategy:

src/core/kilocode/task/message-utils.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/core/task/Task.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ import { ensureLocalKilorulesDirExists } from "../context/instructions/kilo-rule
119119
import { getMessagesSinceLastSummary, summarizeConversation } from "../condense"
120120
import { Gpt5Metadata, ClineMessageWithMetadata } from "./types"
121121
import { MessageQueueService } from "../message-queue/MessageQueueService"
122-
import { findPartialAskMessage, findPartialSayMessage } from "../kilocode/task/message-utils" // kilocode_change
123122

124123
import { AutoApprovalHandler } from "./AutoApprovalHandler"
125124
import { isAnyRecognizedKiloCodeError, isPaymentRequiredError } from "../../shared/kilocode/errorUtils"
@@ -769,13 +768,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
769768
let askTs: number
770769

771770
if (partial !== undefined) {
772-
// kilocode_change start: Fix orphaned partial asks by searching backwards
773-
// Search for the most recent partial ask of this type, handling cases where
774-
// non-interactive messages (like checkpoint_saved) are inserted during streaming
775-
const partialResult = findPartialAskMessage(this.clineMessages, type)
776-
const lastMessage = partialResult?.message
777-
const isUpdatingPreviousPartial = lastMessage !== undefined
778-
// kilocode_change end
771+
const lastMessage = this.clineMessages.at(-1)
772+
773+
const isUpdatingPreviousPartial =
774+
lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type
779775

780776
if (partial) {
781777
if (isUpdatingPreviousPartial) {
@@ -1161,12 +1157,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
11611157
}
11621158

11631159
if (partial !== undefined) {
1164-
// kilocode_change start: Fix orphaned partial says by searching backwards
1165-
// Search for the most recent partial say of this type
1166-
const partialResult = findPartialSayMessage(this.clineMessages, type)
1167-
const lastMessage = partialResult?.message
1168-
const isUpdatingPreviousPartial = lastMessage !== undefined
1169-
// kilocode_change end
1160+
const lastMessage = this.clineMessages.at(-1)
1161+
1162+
const isUpdatingPreviousPartial =
1163+
lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type
11701164

11711165
if (partial) {
11721166
if (isUpdatingPreviousPartial) {

src/core/task/__tests__/message-utils.spec.ts

Lines changed: 0 additions & 124 deletions
This file was deleted.

0 commit comments

Comments
 (0)