diff --git a/.changeset/fix-queued-message-state.md b/.changeset/fix-queued-message-state.md new file mode 100644 index 00000000000..8da3bef4a24 --- /dev/null +++ b/.changeset/fix-queued-message-state.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Fix queued-state detection so prompts sent after a completed response are treated as active instead of queued. diff --git a/packages/kilo-vscode/tests/unit/session-queue.test.ts b/packages/kilo-vscode/tests/unit/session-queue.test.ts new file mode 100644 index 00000000000..633d4411b3c --- /dev/null +++ b/packages/kilo-vscode/tests/unit/session-queue.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "bun:test" +import { activeUserMessageID } from "../../webview-ui/src/context/session-queue" +import type { Message } from "../../webview-ui/src/types/messages" + +const base = { + sessionID: "session", + createdAt: "2026-01-01T00:00:00.000Z", + time: { created: 1 }, +} + +const user = (id: string): Message => ({ ...base, id, role: "user" }) + +const assistant = (id: string, parentID: string, opts: Partial = {}): Message => ({ + ...base, + id, + parentID, + role: "assistant", + ...opts, +}) + +describe("activeUserMessageID", () => { + it("ignores terminal assistant updates without completed timestamps", () => { + const messages = [user("message_1"), assistant("message_2", "message_1", { finish: "stop" }), user("message_3")] + + expect(activeUserMessageID(messages, { type: "busy" })).toBe("message_3") + }) + + it("keeps tool-call assistants active until their follow-up finishes", () => { + const messages = [ + user("message_1"), + assistant("message_2", "message_1", { finish: "tool-calls" }), + user("message_3"), + ] + + expect(activeUserMessageID(messages, { type: "busy" })).toBe("message_1") + }) + + it("keeps unknown assistants active until cleanup finishes", () => { + const messages = [user("message_1"), assistant("message_2", "message_1", { finish: "unknown" }), user("message_3")] + + expect(activeUserMessageID(messages, { type: "busy" })).toBe("message_1") + }) +}) diff --git a/packages/kilo-vscode/webview-ui/src/context/session-queue.ts b/packages/kilo-vscode/webview-ui/src/context/session-queue.ts index ac64139585b..7ae6b63117a 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session-queue.ts +++ b/packages/kilo-vscode/webview-ui/src/context/session-queue.ts @@ -3,18 +3,17 @@ import type { Message, SessionStatusInfo } from "../types/messages" // Find the user message whose turn the server is actively processing. // Any user message after this one is "queued" (waiting for its turn). export function activeUserMessageID(messages: Message[], status: SessionStatusInfo) { - // Walk backward to find a non-completed assistant — its parent is the active turn for (let i = messages.length - 1; i >= 0; i -= 1) { const msg = messages[i] if (msg.role !== "assistant") continue if (typeof msg.time?.completed === "number") continue + if (msg.finish && !["tool-calls", "unknown"].includes(msg.finish)) continue if (!msg.parentID) break const parent = messages.find((item) => item.id === msg.parentID) if (parent?.role === "user") return parent.id break } - // No pending assistant found — if busy, the last user message is the active turn if (status.type === "idle") return undefined for (let i = messages.length - 1; i >= 0; i -= 1) { diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 7a2254bfa5f..c40901cdb59 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -125,6 +125,7 @@ export interface Message { summary?: { title?: string; body?: string; diffs?: unknown[] } | boolean cost?: number tokens?: TokenUsage + finish?: string } // File diff info (matches Snapshot.FileDiff from CLI backend)