diff --git a/.changeset/compact-thinking-level.md b/.changeset/compact-thinking-level.md new file mode 100644 index 00000000000..a00bd74f635 --- /dev/null +++ b/.changeset/compact-thinking-level.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": patch +"kilo-code": patch +--- + +Preserve the selected thinking level after compacting a session. diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 8f504683627..c6bb8500f31 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -43,6 +43,7 @@ import { DialogSkill } from "../dialog-skill" import { DialogWorkspaceCreate, restoreWorkspaceSession } from "../dialog-workspace-create" import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { useArgs } from "@tui/context/args" +import { KiloSessionTuiSync } from "@/kilocode/session/tui-sync" // kilocode_change export type PromptProps = { sessionID?: string @@ -249,6 +250,11 @@ export function Prompt(props: PromptProps) { const sessionID = props.sessionID const msg = lastUserMessage() if (!sessionID || !msg) return + // kilocode_change start - skip compaction messages while syncing local agent/model + const parts = sync.data.part[msg.id] + if (!parts) return + if (!KiloSessionTuiSync.model({ role: msg.role, parts })) return + // kilocode_change end const key = [sessionID, msg.id].join(":") if (key === syncedKey) return diff --git a/packages/opencode/src/kilocode/session/tui-sync.ts b/packages/opencode/src/kilocode/session/tui-sync.ts new file mode 100644 index 00000000000..26d71f9ba64 --- /dev/null +++ b/packages/opencode/src/kilocode/session/tui-sync.ts @@ -0,0 +1,7 @@ +export namespace KiloSessionTuiSync { + export function model(input: { role: string; parts?: readonly { type: string }[] }) { + if (input.role !== "user") return false + if (!input.parts) return false + return !input.parts.some((part) => part.type === "compaction") + } +} diff --git a/packages/opencode/test/kilocode/tui-sync.test.ts b/packages/opencode/test/kilocode/tui-sync.test.ts new file mode 100644 index 00000000000..e7262ac74e2 --- /dev/null +++ b/packages/opencode/test/kilocode/tui-sync.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "bun:test" +import { KiloSessionTuiSync } from "../../src/kilocode/session/tui-sync" + +type Message = { + role: string + model?: { providerID: string; modelID: string; variant?: string } + parts?: readonly { type: string }[] +} + +function syncVariant(input: { current: string | undefined; message: Message; parts?: readonly { type: string }[] }) { + if (!KiloSessionTuiSync.model({ role: input.message.role, parts: input.parts })) return input.current + return input.message.model?.variant ?? "default" +} + +describe("KiloSessionTuiSync.model", () => { + test("syncs normal user messages", () => { + expect(KiloSessionTuiSync.model({ role: "user", parts: [{ type: "text" }] })).toBe(true) + }) + + test("skips compaction marker user messages", () => { + expect(KiloSessionTuiSync.model({ role: "user", parts: [{ type: "compaction" }] })).toBe(false) + }) + + test("skips messages before parts load", () => { + expect(KiloSessionTuiSync.model({ role: "user" })).toBe(false) + }) + + test("skips messages checked with stored parts", () => { + const msg = { role: "user" } + const parts = [{ type: "compaction" }] + + expect(KiloSessionTuiSync.model({ role: msg.role, parts })).toBe(false) + }) + + test("skips non-user messages", () => { + expect(KiloSessionTuiSync.model({ role: "assistant", parts: [{ type: "text" }] })).toBe(false) + }) + + test("preserves thinking level after /compact", () => { + const msg = { + role: "user", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5" }, + parts: [{ type: "compaction" }], + } + + expect(syncVariant({ current: "high", message: msg, parts: msg.parts })).toBe("high") + }) + + test("preserves thinking level when compaction parts are stored separately", () => { + const msg = { + role: "user", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5" }, + } + const parts = [{ type: "compaction" }] + + expect(parts.some((part) => part.type === "compaction")).toBe(true) + expect(syncVariant({ current: "high", message: msg, parts })).toBe("high") + }) + + test("waits for normal user message parts before syncing", () => { + const msg = { + role: "user", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5", variant: "max" }, + } + const parts = [{ type: "text" }] + + expect(syncVariant({ current: "high", message: msg })).toBe("high") + expect(syncVariant({ current: "high", message: msg, parts })).toBe("max") + }) + + test("still updates thinking level from normal user messages", () => { + const msg = { + role: "user", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-5", variant: "max" }, + parts: [{ type: "text" }], + } + + expect(syncVariant({ current: "high", message: msg, parts: msg.parts })).toBe("max") + }) +})