diff --git a/.changeset/renderer-memory-guardrails.md b/.changeset/renderer-memory-guardrails.md new file mode 100644 index 00000000000..b61adaf1be2 --- /dev/null +++ b/.changeset/renderer-memory-guardrails.md @@ -0,0 +1,6 @@ +--- +"kilo-code": patch +"@kilocode/cli": patch +--- + +Reduce memory use when switching long VS Code sessions while preserving queued prompts, unsent drafts, and inline edit diffs. diff --git a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts index df8d576e030..6ed59c04173 100644 --- a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts +++ b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts @@ -182,6 +182,63 @@ function slimBash(state: Record): Record { return next } +/** websearch: preserve provider metadata and cap rendered results. */ +function slimWebsearch(state: Record): Record { + const next = slimOutput(state) + const meta = state.metadata + if (isObj(meta)) { + const slim: Record = {} + if (typeof meta.provider === "string") slim.provider = meta.provider + next.metadata = slim + } + return next +} + +/** webfetch: preserve the requested URL and cap rendered response content. */ +function slimWebfetch(state: Record): Record { + const next = slimOutput(state) + const input = state.input + if (isObj(input)) { + const slim: Record = {} + if (typeof input.url === "string") slim.url = input.url + next.input = slim + } + return next +} + +/** codesearch: preserve query/token count and cap rendered results. */ +function slimCodesearch(state: Record): Record { + const next = slimOutput(state) + const input = state.input + if (isObj(input)) { + const slim: Record = {} + if (typeof input.query === "string") slim.query = input.query + if (typeof input.tokensNum === "number") slim.tokensNum = input.tokensNum + next.input = slim + } + return next +} + +/** task: preserve rendered labels and sessionId while capping sub-agent output. */ +function slimTask(state: Record): Record { + const next = slimOutput(state) + const input = state.input + if (isObj(input)) { + const slim: Record = {} + if (typeof input.description === "string") slim.description = input.description + if (typeof input.subagent_type === "string") slim.subagent_type = input.subagent_type + if (typeof input.sessionId === "string") slim.sessionId = input.sessionId + next.input = slim + } + const meta = state.metadata + if (isObj(meta) && typeof meta.sessionId === "string") { + next.metadata = { sessionId: meta.sessionId } + } else { + delete next.metadata + } + return next +} + // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- @@ -214,6 +271,10 @@ const slimmers: Record) => Record", start) + return source.slice(start, end) +} + +describe("MessageList rendering", () => { + it("marks only partitioned queued turns as queued", () => { + expect(virtualizer()).not.toContain("queued=") + expect(source).toMatch(/[\s\S]*?/) + }) + + it("disables automatic history expansion when configured", () => { + expect(source).toContain("config().auto_expand_history === false") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/prompt-drafts.test.ts b/packages/kilo-vscode/tests/unit/prompt-drafts.test.ts index d5f9dc532de..882d78b0f44 100644 --- a/packages/kilo-vscode/tests/unit/prompt-drafts.test.ts +++ b/packages/kilo-vscode/tests/unit/prompt-drafts.test.ts @@ -1,6 +1,31 @@ -import { describe, it, expect } from "bun:test" +import { beforeEach, describe, it, expect } from "bun:test" +import { deleteDraftsForSession, drafts, imageDrafts, reviewDrafts } from "../../webview-ui/src/utils/draft-store" import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../webview-ui/src/utils/prompt-drafts" +beforeEach(() => { + drafts.clear() + reviewDrafts.clear() + imageDrafts.clear() +}) + +describe("deleteDraftsForSession", () => { + it("clears deleted-session drafts without touching other sessions", () => { + drafts.set("prompt:default:session:a", "draft a") + drafts.set("prompt:default:pending:a", "pending a") + drafts.set("prompt:default:session:b", "draft b") + reviewDrafts.set("prompt:default:session:a", []) + imageDrafts.set("prompt:default:session:a", []) + + deleteDraftsForSession("a") + + expect(drafts.has("prompt:default:session:a")).toBe(false) + expect(drafts.has("prompt:default:pending:a")).toBe(false) + expect(drafts.get("prompt:default:session:b")).toBe("draft b") + expect(reviewDrafts.has("prompt:default:session:a")).toBe(false) + expect(imageDrafts.has("prompt:default:session:a")).toBe(false) + }) +}) + describe("sessionDraftKey", () => { it("prefixes session ids", () => { expect(sessionDraftKey("abc")).toBe("session:abc") diff --git a/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts b/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts index e9f8126a56e..8ca789c7aa6 100644 --- a/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts @@ -17,6 +17,7 @@ import path from "node:path" const ROOT = path.resolve(import.meta.dir, "../..") const SESSION_FILE = path.join(ROOT, "webview-ui/src/context/session.tsx") const CHATVIEW_FILE = path.join(ROOT, "webview-ui/src/components/chat/ChatView.tsx") +const PROMPT_INPUT_FILE = path.join(ROOT, "webview-ui/src/components/chat/PromptInput.tsx") const PROMPT_UTILS_FILE = path.join(ROOT, "webview-ui/src/components/chat/prompt-input-utils.ts") function readFile(filePath: string): string { @@ -75,6 +76,32 @@ describe("sendCommand dismisses pending tool requests", () => { }) }) +describe("session draft retention contract", () => { + const source = readFile(SESSION_FILE) + const input = readFile(PROMPT_INPUT_FILE) + const select = extractFunctionBody(source, "selectSession") + const remove = extractFunctionBody(source, "handleSessionDeleted") + + it("does not treat a session switch as draft deletion", () => { + expect(select).not.toContain("deleteDraftsForSession") + expect(select).not.toContain("sessionDeleted") + }) + + it("cleans the draft maps only on real deletion", () => { + expect(input).toContain('import { drafts, imageDrafts, reviewDrafts } from "../../utils/draft-store"') + expect(remove).toContain("deleteDraftsForSession(sessionID)") + }) + + it("clears off-store transcript caches unless the session becomes active again", () => { + const guard = select.indexOf("if (currentSessionID() === oldID) return") + const cleanup = select.indexOf("stash.remove(mid)") + + expect(guard).toBeGreaterThan(-1) + expect(cleanup).toBeGreaterThan(guard) + expect(select).toContain("delete map[oldID]") + }) +}) + describe("ChatView prompt-block contract", () => { const source = readFile(CHATVIEW_FILE) diff --git a/packages/kilo-vscode/tests/unit/slim-metadata.test.ts b/packages/kilo-vscode/tests/unit/slim-metadata.test.ts index 21c62f4722c..5e69f62133f 100644 --- a/packages/kilo-vscode/tests/unit/slim-metadata.test.ts +++ b/packages/kilo-vscode/tests/unit/slim-metadata.test.ts @@ -367,4 +367,72 @@ describe("slimPart", () => { expect(bytes(slimPart(heavy))).toBeLessThan(MAX_SLIM_BYTES) }) }) + + // ----------------------------------------------------------------------- + // websearch / webfetch / codesearch / task + // ----------------------------------------------------------------------- + describe("web tools", () => { + it("caps websearch output and preserves provider metadata", () => { + const heavy = part("websearch", { + status: "completed", + input: { query: "renderer memory" }, + output: BIG, + metadata: { provider: "exa", results: BIG }, + }) + const slim = slimPart(heavy) as Record + + expect(slim.state.output.length).toBeLessThan(BIG.length) + expect(slim.state.metadata).toEqual({ provider: "exa" }) + expect(bytes(slim)).toBeLessThan(MAX_SLIM_BYTES) + }) + + it("caps webfetch output and preserves only the URL input", () => { + const heavy = part("webfetch", { + status: "completed", + input: { url: "https://example.com", format: "markdown" }, + output: BIG, + metadata: {}, + }) + const slim = slimPart(heavy) as Record + + expect(slim.state.output.length).toBeLessThan(BIG.length) + expect(slim.state.input).toEqual({ url: "https://example.com" }) + expect(bytes(slim)).toBeLessThan(MAX_SLIM_BYTES) + }) + + it("caps codesearch output and preserves query/token inputs", () => { + const heavy = part("codesearch", { + status: "completed", + input: { query: "SolidJS createMemo", tokensNum: 5000, extra: BIG }, + output: BIG, + metadata: {}, + }) + const slim = slimPart(heavy) as Record + + expect(slim.state.output.length).toBeLessThan(BIG.length) + expect(slim.state.input).toEqual({ query: "SolidJS createMemo", tokensNum: 5000 }) + expect(bytes(slim)).toBeLessThan(MAX_SLIM_BYTES) + }) + }) + + describe("task", () => { + it("caps output and preserves only rendered input plus child session metadata", () => { + const heavy = part("task", { + status: "completed", + input: { description: "Explore renderer", prompt: BIG, subagent_type: "explore", sessionId: "child" }, + output: BIG, + metadata: { sessionId: "child", model: BIG }, + }) + const slim = slimPart(heavy) as Record + + expect(slim.state.output.length).toBeLessThan(BIG.length) + expect(slim.state.input).toEqual({ + description: "Explore renderer", + subagent_type: "explore", + sessionId: "child", + }) + expect(slim.state.metadata).toEqual({ sessionId: "child" }) + expect(bytes(slim)).toBeLessThan(MAX_SLIM_BYTES) + }) + }) }) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index 702bd3a680e..0ee25292f5e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -17,6 +17,7 @@ import { createAutoScroll } from "@kilocode/kilo-ui/hooks" import { useSession } from "../../context/session" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" +import { useConfig } from "../../context/config" import { recentSessions } from "../../context/session-utils" import { formatRelativeDate } from "../../utils/date" import { FeedbackDialog } from "./FeedbackDialog" @@ -68,6 +69,7 @@ export const MessageList: Component = (props) => { const session = useSession() const server = useServer() const language = useLanguage() + const { config } = useConfig() const dialog = useDialog() const autoScroll = createAutoScroll({ @@ -143,7 +145,7 @@ export const MessageList: Component = (props) => { const maybeLoadOlder = () => { const el = scrollEl() - if (!el || el.scrollTop > 600) return + if (!el || el.scrollTop > 600 || config().auto_expand_history === false) return session.loadOlderMessages() } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 898301bc4e7..52db8c1bb4f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -48,13 +48,9 @@ import { } from "./prompt-input-utils" import type { ReviewComment, TextPart } from "../../types/messages" import { formatReviewCommentsMarkdown } from "../../utils/review-comment-markdown" +import { drafts, imageDrafts, reviewDrafts } from "../../utils/draft-store" import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../utils/prompt-drafts" -// Per-session input text storage (module-level so it survives remounts) -const drafts = new Map() -const reviewDrafts = new Map() -const imageDrafts = new Map() - function mergeReviewComments(current: ReviewComment[], incoming: ReviewComment[]): ReviewComment[] { if (incoming.length === 0) return current const map = new Map(current.map((item) => [item.id, item])) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx index 978d295e18f..19a1bdb2dfc 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx @@ -99,6 +99,19 @@ const DisplayTab: Component = () => { + + updateConfig({ auto_expand_history: checked ? undefined : false })} + hideLabel + > + {language.t("settings.display.autoExpandHistory.title")} + + + = [ "tools", "layout", "auto_collapse_reasoning", + "auto_expand_history", "terminal_command_display", "indexing", "experimental", diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index d93c05ffce5..989b116d488 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -60,6 +60,7 @@ import { state as todoState } from "./todo-revert" import { getVariant, sessionVariantKeys, transferVariants, variantKey } from "./session-variant-store" import { KILO_AUTO, parseModelString } from "../../../src/shared/provider-model" import { visibleMessages as filterVisibleMessages } from "./session-queue" +import { deleteDraftsForSession } from "../utils/draft-store" const RECENT_LIMIT = 5 const MESSAGE_PAGE_LIMIT = 80 @@ -1700,6 +1701,7 @@ export const SessionProvider: ParentComponent = (props) => { setLoading(false) } }) + deleteDraftsForSession(sessionID) } // Splices the message from the store and deletes its parts. @@ -2190,7 +2192,53 @@ export const SessionProvider: ParentComponent = (props) => { console.warn("[Kilo New] Cannot select cloud preview session via selectSession") return } + const oldID = currentSessionID() const ready = loaded().has(id) + if (oldID && oldID !== id) { + const msgs = store.messages[oldID] ?? [] + const msgIds = msgs.map((m) => m.id) + queueMicrotask(() => { + if (currentSessionID() === oldID) return + for (const mid of msgIds) stash.remove(mid) + setStore( + "messages", + produce((messages) => { + delete messages[oldID] + }), + ) + setStore( + "parts", + produce((parts) => { + for (const mid of msgIds) { + delete parts[mid] + } + }), + ) + setStore( + "todos", + produce((todos) => { + delete todos[oldID] + }), + ) + setStore( + "agentSelections", + produce((selections) => { + delete selections[oldID] + }), + ) + setPages( + produce((map) => { + delete map[oldID] + }), + ) + setLoaded((prev) => { + if (!prev.has(oldID)) return prev + const next = new Set(prev) + next.delete(oldID) + return next + }) + }) + } setCurrentSessionID(id) setDraftSessionID(id) setLoading(!ready) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 81ca9c72287..a27e13b1e7f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1448,6 +1448,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "تمديد سجل الرسائل تلقائيًا", + "settings.display.autoExpandHistory.description": "تحميل الرسائل السابقة تلقائيًا عند التمرير للأعلى", "settings.providers.defaultModel.title": "النموذج الافتراضي", "settings.providers.defaultModel.description": "النموذج الأساسي للمحادثات", "settings.providers.smallModel.title": "نموذج صغير", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 0b1d733a33c..056426d7e21 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1491,6 +1491,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Expandir histórico de mensagens automaticamente", + "settings.display.autoExpandHistory.description": "Carregar mensagens anteriores automaticamente ao rolar para cima", "settings.providers.defaultModel.title": "Modelo padrão", "settings.providers.defaultModel.description": "Modelo principal para conversas", "settings.providers.smallModel.title": "Modelo pequeno", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index b7f0256903f..6129140fcb3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1487,6 +1487,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Automatsko proširivanje historije poruka", + "settings.display.autoExpandHistory.description": "Automatski učitaj ranije poruke pri pomicanju nagore", "settings.providers.defaultModel.title": "Zadani model", "settings.providers.defaultModel.description": "Primarni model za razgovore", "settings.providers.smallModel.title": "Mali model", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 4610c0aaccc..848877dc710 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1475,6 +1475,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Udvid beskedoversigt automatisk", + "settings.display.autoExpandHistory.description": "Indlæs automatisk tidligere beskeder ved scrolling opad", "settings.providers.defaultModel.title": "Standardmodel", "settings.providers.defaultModel.description": "Primær model til samtaler", "settings.providers.smallModel.title": "Lille model", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index ed2baf0392d..a7bccc34c33 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1505,6 +1505,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Nachrichtenverlauf automatisch erweitern", + "settings.display.autoExpandHistory.description": "Beim Hochscrollen automatisch frühere Nachrichten laden", "settings.providers.defaultModel.title": "Standardmodell", "settings.providers.defaultModel.description": "Primäres Modell für Gespräche", "settings.providers.smallModel.title": "Kleines Modell", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index df91b0b6e1f..ad4acbde790 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1462,6 +1462,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Auto-Expand Message History", + "settings.display.autoExpandHistory.description": "Automatically load earlier messages when scrolling up", "settings.providers.defaultModel.title": "Default Model", "settings.providers.defaultModel.description": "Primary model for conversations", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 1f07e5d1e7f..f82b3e716d5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1498,6 +1498,9 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Expandir histórico de mensajes automáticamente", + "settings.display.autoExpandHistory.description": + "Cargar mensajes anteriores automáticamente al desplazarse hacia arriba", "settings.providers.defaultModel.title": "Modelo predeterminado", "settings.providers.defaultModel.description": "Modelo principal para conversaciones", "settings.providers.smallModel.title": "Modelo pequeño", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 09b25f89c3b..3850d3eb940 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1514,6 +1514,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Développer automatiquement l'historique des messages", + "settings.display.autoExpandHistory.description": "Charger automatiquement les messages précédents en remontant", "settings.providers.defaultModel.title": "Modèle par défaut", "settings.providers.defaultModel.description": "Modèle principal pour les conversations", "settings.providers.smallModel.title": "Petit modèle", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index 5776e3f16d0..f9c34323476 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -1305,6 +1305,9 @@ export const dict = { "settings.display.reasoningAutoCollapse.title": "Comprimi automaticamente ragionamento", "settings.display.reasoningAutoCollapse.description": "Comprimi i blocchi di ragionamento dopo che l'agente ha finito di scriverli. Lascia disattivato per tenerli espansi finché non li comprimi manualmente.", + "settings.display.autoExpandHistory.title": "Espandi automaticamente la cronologia dei messaggi", + "settings.display.autoExpandHistory.description": + "Carica automaticamente i messaggi precedenti quando scorri verso l'alto", "settings.display.terminalCommand.title": "Blocchi comando terminale", "settings.display.terminalCommand.description": "Scegli se i blocchi comando terminale iniziano espansi o compressi.", "settings.display.terminalCommand.expanded": "Espansi", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 46cab151069..b4402f8cdca 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1471,6 +1471,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "メッセージ履歴を自動展開", + "settings.display.autoExpandHistory.description": "上にスクロール時に以前のメッセージを自動読み込み", "settings.providers.defaultModel.title": "デフォルトモデル", "settings.providers.defaultModel.description": "会話のプライマリモデル", "settings.providers.smallModel.title": "小型モデル", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index dd805c59758..b78064fe884 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1456,6 +1456,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "메시지 기록 자동 펴치기", + "settings.display.autoExpandHistory.description": "위로 스크롤 시 이전 메시지 자동 로드", "settings.providers.defaultModel.title": "기본 모델", "settings.providers.defaultModel.description": "대화의 기본 모델", "settings.providers.smallModel.title": "소형 모델", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 33ba31990cb..5394be3d0e8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1452,6 +1452,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Berichtgeschiedenis automatisch uitvouwen", + "settings.display.autoExpandHistory.description": "Eerdere berichten automatisch laden bij het omhoog scrollen", "settings.providers.defaultModel.title": "Standaard Model", "settings.providers.defaultModel.description": "Primair model voor gesprekken", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 6078672537d..d9caf0a610e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1474,6 +1474,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Utvid meldingsloggen automatisk", + "settings.display.autoExpandHistory.description": "Last inn tidligere meldinger automatisk når du ruller opp", "settings.providers.defaultModel.title": "Standardmodell", "settings.providers.defaultModel.description": "Primær modell for samtaler", "settings.providers.smallModel.title": "Liten modell", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 7caf5f74b9c..3cddef95e33 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1482,6 +1482,9 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Automatyczne rozwijanie historii wiadomości", + "settings.display.autoExpandHistory.description": + "Automatycznie ładuj wcześniejsze wiadomości podczas przewijania w górę", "settings.providers.defaultModel.title": "Domyślny model", "settings.providers.defaultModel.description": "Główny model do rozmów", "settings.providers.smallModel.title": "Mały model", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 681e235eb80..c4a84bb56b4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1481,6 +1481,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Автоматически разворачивать историю сообщений", + "settings.display.autoExpandHistory.description": "Автоматически загружать предыдущие сообщения при прокрутке вверх", "settings.providers.defaultModel.title": "Модель по умолчанию", "settings.providers.defaultModel.description": "Основная модель для разговоров", "settings.providers.smallModel.title": "Малая модель", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 22ecabbc9dd..dacc499d4ba 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1455,6 +1455,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "ขยายประวัติข้อความอัตโนมัติ", + "settings.display.autoExpandHistory.description": "โหลดข้อความก่อนหน้าโดยอัตโนมัติเมื่อเลื่อนขึ้น", "settings.providers.defaultModel.title": "โมเดลเริ่มต้น", "settings.providers.defaultModel.description": "โมเดลหลักสำหรับบทสนทนา", "settings.providers.smallModel.title": "โมเดลขนาดเล็ก", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 6c5da55af38..1b37fffe860 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1441,6 +1441,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Mesaj geçmişini otomatik olarak genişlet", + "settings.display.autoExpandHistory.description": "Yukarı kaydırırken önceki mesajları otomatik olarak yükle", "settings.providers.defaultModel.title": "Varsayılan Model", "settings.providers.defaultModel.description": "Sohbetler için birincil model", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 5d2ff53eb80..a0e7a84e65e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1441,6 +1441,9 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "Автоматично розгортати історію повідомлень", + "settings.display.autoExpandHistory.description": + "Автоматично завантажувати попередні повідомлення під час прокручування вгору", "settings.providers.defaultModel.title": "Модель за замовчуванням", "settings.providers.defaultModel.description": "Основна модель для чатів", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 05316ebf229..542265c2303 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1420,6 +1420,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "自动展开消息历史", + "settings.display.autoExpandHistory.description": "向上滚动时自动加载更早的消息", "settings.providers.defaultModel.title": "默认模型", "settings.providers.defaultModel.description": "对话的主要模型", "settings.providers.smallModel.title": "小模型", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 00d4bfd4d45..155b060eea4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1390,6 +1390,8 @@ export const dict = { "settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.", "settings.display.terminalCommand.expanded": "Expanded", "settings.display.terminalCommand.collapsed": "Collapsed", + "settings.display.autoExpandHistory.title": "自動展開訊息紀錄", + "settings.display.autoExpandHistory.description": "向上捲動時自動載入較早的訊息", "settings.providers.defaultModel.title": "預設模型", "settings.providers.defaultModel.description": "對話的主要模型", "settings.providers.smallModel.title": "小模型", diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts index bd47ed509b8..4f112f308bb 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts @@ -139,6 +139,7 @@ export interface Config { tools?: Record layout?: "auto" | "stretch" auto_collapse_reasoning?: boolean + auto_expand_history?: boolean experimental?: ExperimentalConfig indexing?: IndexingConfig } diff --git a/packages/kilo-vscode/webview-ui/src/utils/draft-store.ts b/packages/kilo-vscode/webview-ui/src/utils/draft-store.ts new file mode 100644 index 00000000000..daf3d673604 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/utils/draft-store.ts @@ -0,0 +1,20 @@ +import type { ReviewComment } from "../types/messages" +import type { ImageAttachment } from "../hooks/useImageAttachments" + +export const drafts = new Map() +export const reviewDrafts = new Map() +export const imageDrafts = new Map() + +export function deleteDraftsForSession(id: string) { + if (!id) return + const suffix = `:session:${id}` + const pendingSuffix = `:pending:${id}` + const maps = [drafts, reviewDrafts, imageDrafts] + for (const map of maps) { + for (const key of map.keys()) { + if (typeof key === "string" && (key.endsWith(suffix) || key.endsWith(pendingSuffix))) { + map.delete(key) + } + } + } +} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 80e7850bdb4..68963a18e3b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -218,6 +218,11 @@ export const Info = Schema.Struct({ auto_collapse_reasoning: Schema.optional(Schema.Boolean).annotate({ description: "Automatically collapse reasoning blocks after the agent finishes writing them", }), + // kilocode_change start + auto_expand_history: Schema.optional(Schema.Boolean).annotate({ + description: "Auto-expand message history when scrolling up. Defaults to true.", + }), + // kilocode_change end indexing: Schema.optional(IndexingRef).annotate({ description: "Codebase indexing configuration" }), // kilocode_change terminal_command_display: Schema.optional(Schema.Literals(["expanded", "collapsed"])).annotate({ description: "Controls whether terminal command blocks are expanded or collapsed by default in the VS Code chat UI", diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 70abefb6909..09c03084935 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -681,9 +681,11 @@ export const cursor = { // kilocode_change start - strip bloated metadata fields from stored parts to prevent multi-MB payloads // This handles both legacy data that was stored with full file contents and keeps the API response lean. -function stripPatch(value: unknown) { +const MAX_INLINE_DIFF_SIZE = 64 * 1024 + +function stripPatch(value: unknown, limit = Snapshot.MAX_DIFF_SIZE) { if (typeof value !== "string") return undefined - if (Buffer.byteLength(value) > Snapshot.MAX_DIFF_SIZE) return undefined + if (Buffer.byteLength(value) > limit) return undefined return value } @@ -705,7 +707,8 @@ export function stripPartMetadata(part: Part): Part { if (meta.diff !== undefined) { const { diff, ...rest } = next - next = rest + const kept = stripPatch(diff, MAX_INLINE_DIFF_SIZE) + next = { ...rest, ...(kept ? { diff: kept } : {}) } changed = true } diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index c823f9cb611..6ed26a364cb 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -643,7 +643,7 @@ export const layer: Layer.Layer SyncEvent.run(MessageV2.Event.PartUpdated, { sessionID: part.sessionID, - part: structuredClone(part), + part: structuredClone(MessageV2.stripPartMetadata(part)), time: Date.now(), }), { type: "part update", id: part.id, sessionID: part.sessionID }, diff --git a/packages/opencode/test/kilocode/session-message-metadata.test.ts b/packages/opencode/test/kilocode/session-message-metadata.test.ts index 5fa865fb0c0..2a8e1665130 100644 --- a/packages/opencode/test/kilocode/session-message-metadata.test.ts +++ b/packages/opencode/test/kilocode/session-message-metadata.test.ts @@ -33,6 +33,14 @@ function part(tool: string, metadata: Record): MessageV2.Part { } describe("session message metadata stripping", () => { + test("keeps bounded edit diffs for live renderers", () => { + const input = part("edit", { diff: patch }) + const stripped = MessageV2.stripPartMetadata(input) as Extract + const meta = stripped.state.status === "completed" ? stripped.state.metadata : {} + + expect(meta.diff).toBe(patch) + }) + test("keeps bounded edit filediff patches and strips heavy fields", () => { const input = part("edit", { diff: blob(200_000), diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a0fb916a27b..7fe90c51c92 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1357,6 +1357,7 @@ export type Config = { enabled_providers?: Array remote_control?: boolean auto_collapse_reasoning?: boolean + auto_expand_history?: boolean indexing?: IndexingConfig terminal_command_display?: "expanded" | "collapsed" model?: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 6f845c28f5c..a2a3944a39e 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -17833,6 +17833,9 @@ "auto_collapse_reasoning": { "type": "boolean" }, + "auto_expand_history": { + "type": "boolean" + }, "indexing": { "$ref": "#/components/schemas/IndexingConfig" },