diff --git a/.changeset/subagent-task-header.md b/.changeset/subagent-task-header.md new file mode 100644 index 00000000000..86580f310f3 --- /dev/null +++ b/.changeset/subagent-task-header.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Allow subagent task headers to collapse and share the saved timeline preference with the main agent. diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 55cbba341aa..a9814cbad00 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -188,7 +188,7 @@ import { type ConfigTarget, } from "./kilo-provider/config-bindings" import { canonicalizePath, projectIdFor, samePath } from "./agent-manager/project/paths" -import { validChatSetting, watchChatConfig } from "./kilo-provider/chat-settings" +import { buildTimelineSettingMessage, validChatSetting, watchChatConfig } from "./kilo-provider/chat-settings" import { buildThroughputSettingMessage, watchThroughputConfig } from "./kilo-provider/throughput-settings" import { buildAutoApprovalReasonSettingMessage, @@ -3139,11 +3139,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } private sendTimelineSetting(): void { - const config = vscode.workspace.getConfiguration("kilo-code.new") - this.postMessage({ - type: "timelineSettingLoaded", - visible: config.get("showTaskTimeline", true), - }) + this.postMessage(buildTimelineSettingMessage()) } private sendWorkStyle(): void { diff --git a/packages/kilo-vscode/src/kilo-provider/chat-settings.ts b/packages/kilo-vscode/src/kilo-provider/chat-settings.ts index a1a7c9427b3..8defb9dae74 100644 --- a/packages/kilo-vscode/src/kilo-provider/chat-settings.ts +++ b/packages/kilo-vscode/src/kilo-provider/chat-settings.ts @@ -12,11 +12,22 @@ export function buildChatSettingsMessage() { } } +export function buildTimelineSettingMessage() { + const config = vscode.workspace.getConfiguration("kilo-code.new") + return { + type: "timelineSettingLoaded" as const, + visible: config.get("showTaskTimeline", true), + } +} + export function watchChatConfig(post: Post): vscode.Disposable { return vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration("kilo-code.new.chat")) { post(buildChatSettingsMessage()) } + if (event.affectsConfiguration("kilo-code.new.showTaskTimeline")) { + post(buildTimelineSettingMessage()) + } }) } diff --git a/packages/kilo-vscode/tests/unit/chat-settings-message.test.ts b/packages/kilo-vscode/tests/unit/chat-settings-message.test.ts index 0671910d3fc..87c6c7e2e87 100644 --- a/packages/kilo-vscode/tests/unit/chat-settings-message.test.ts +++ b/packages/kilo-vscode/tests/unit/chat-settings-message.test.ts @@ -1,18 +1,27 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test" import * as vscode from "vscode" -import { buildChatSettingsMessage, validChatSetting } from "../../src/kilo-provider/chat-settings" +import { + buildChatSettingsMessage, + buildTimelineSettingMessage, + validChatSetting, + watchChatConfig, +} from "../../src/kilo-provider/chat-settings" type Stub = { getConfiguration: (section?: string) => { get: (key: string, fallback?: T) => T | undefined } + onDidChangeConfiguration: (listener: (event: vscode.ConfigurationChangeEvent) => void) => vscode.Disposable } -const original = vscode.workspace.getConfiguration +const original = { + get: vscode.workspace.getConfiguration, + watch: vscode.workspace.onDidChangeConfiguration, +} -function stubConfig(state: Map) { +function stubConfig(state: Map, scope = "kilo-code.new.chat") { ;(vscode.workspace as unknown as Stub).getConfiguration = (section?: string) => { - if (section !== "kilo-code.new.chat") { + if (section !== scope) { return { get: (_key: string, fallback?: T) => fallback } } return { @@ -22,7 +31,9 @@ function stubConfig(state: Map) { } afterEach(() => { - ;(vscode.workspace as unknown as Stub).getConfiguration = original as Stub["getConfiguration"] + const workspace = vscode.workspace as unknown as Stub + workspace.getConfiguration = original.get as Stub["getConfiguration"] + workspace.onDidChangeConfiguration = original.watch }) describe("buildChatSettingsMessage", () => { @@ -44,6 +55,59 @@ describe("buildChatSettingsMessage", () => { }) }) +describe("timeline settings", () => { + it.each([undefined, false, true])("returns the saved visibility %s", (visible) => { + const state = new Map() + if (visible !== undefined) state.set("showTaskTimeline", visible) + stubConfig(state, "kilo-code.new") + + expect(buildTimelineSettingMessage()).toEqual({ + type: "timelineSettingLoaded", + visible: visible ?? true, + }) + }) + + it("synchronizes open viewers and stops sending after disposal", () => { + const state = new Map() + stubConfig(state, "kilo-code.new") + const listeners = new Set<(event: vscode.ConfigurationChangeEvent) => void>() + const workspace = vscode.workspace as unknown as Stub + workspace.onDidChangeConfiguration = (listener) => { + listeners.add(listener) + return new vscode.Disposable(() => listeners.delete(listener)) + } + const emit = (key: string) => { + for (const listener of listeners) listener({ affectsConfiguration: (name) => name === key }) + } + const parent: unknown[] = [] + const child: unknown[] = [] + const main = watchChatConfig((msg) => parent.push(msg)) + const viewer = watchChatConfig((msg) => child.push(msg)) + + emit("kilo-code.new.showTokenThroughput") + expect(parent).toEqual([]) + expect(child).toEqual([]) + + state.set("showTaskTimeline", false) + emit("kilo-code.new.showTaskTimeline") + expect(parent).toEqual([{ type: "timelineSettingLoaded", visible: false }]) + expect(child).toEqual(parent) + + state.set("showTaskTimeline", true) + emit("kilo-code.new.showTaskTimeline") + expect(parent.at(-1)).toEqual({ type: "timelineSettingLoaded", visible: true }) + expect(child).toEqual(parent) + + viewer.dispose() + state.set("showTaskTimeline", false) + emit("kilo-code.new.showTaskTimeline") + expect(parent).toHaveLength(3) + expect(child).toHaveLength(2) + main.dispose() + expect(listeners.size).toBe(0) + }) +}) + describe("validChatSetting", () => { it("accepts only boolean cycling updates", () => { expect(validChatSetting("shiftTabCyclesVariant", true)).toBe(true) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx index e5a2491cc04..f4b243a4971 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx @@ -130,7 +130,6 @@ export const TaskHeader: Component = (props) => { ) const toggle = () => { - if (props.readonly) return const next = !expanded() setExpanded(next) vscode.postMessage({ type: "updateSetting", key: "showTaskTimeline", value: next }) @@ -265,20 +264,14 @@ export const TaskHeader: Component = (props) => { aria-pressed={search.active()} /> - - - +