diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 2b6956e538..ddf6779e06 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -1436,6 +1436,7 @@ export class AgentManagerProvider implements Disposable { /** Push empty state when the folder is not a git repo or has no folder open. */ private pushEmptyState(): void { + void this.activity.sync() this.staleWorktreeIds.clear() this.postToWebview({ type: "agentManager.state", @@ -1616,6 +1617,7 @@ export class AgentManagerProvider implements Disposable { private pushProjects(): void { const projects = this.contexts.snapshots() + void this.activity.sync() this.postToWebview({ type: "agentManager.projects", multiProject: this.host.multiProject(), diff --git a/packages/kilo-vscode/src/agent-manager/worktree-activity.ts b/packages/kilo-vscode/src/agent-manager/worktree-activity.ts index 5dabfceaec..0361221e44 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-activity.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-activity.ts @@ -4,7 +4,7 @@ import type { KiloConnectionService } from "../services/cli-backend" type Snapshot = { statuses: Record permissions: Array<{ id: string; sessionID: string }> - questions: Array<{ id: string; sessionID: string }> + questions: Array<{ id: string; sessionID: string; blocking?: boolean }> } type Change = @@ -234,7 +234,7 @@ export class WorktreeActivity { } state.questions = new Map() for (const item of snapshot.questions ?? []) { - if (typeof item?.id === "string" && typeof item.sessionID === "string") + if (item?.blocking !== false && typeof item?.id === "string" && typeof item.sessionID === "string") state.questions.set(item.id, item.sessionID) } for (const change of req.events) this.apply(state, change) @@ -282,6 +282,7 @@ export class WorktreeActivity { const id = string(value.id) const sessionID = string(value.sessionID) if (!id || !sessionID) return undefined + if (type === "question.asked" && value.blocking === false) return { kind: "question.remove", id, sessionID } return type === "permission.asked" ? { kind: "permission.add", id, sessionID } : { kind: "question.add", id, sessionID } diff --git a/packages/kilo-vscode/tests/unit/project-session-busy.test.ts b/packages/kilo-vscode/tests/unit/project-session-busy.test.ts index a6b386651b..0d85498fe9 100644 --- a/packages/kilo-vscode/tests/unit/project-session-busy.test.ts +++ b/packages/kilo-vscode/tests/unit/project-session-busy.test.ts @@ -30,6 +30,19 @@ describe("createSessionBusy", () => { expect(busy({ working: { type } }).agent("wt-working")).toBe(true) }) + it("keeps running for non-blocking questions", () => { + const questions: { sessionID: string; blocking?: boolean }[] = [{ sessionID: "working", blocking: false }] + const state = createSessionBusy({ + ...options({ working: { type: "busy" } }), + questions: () => questions, + }) + expect(state.agent("wt-working")).toBe(true) + questions[0].blocking = true + expect(state.agent("wt-working")).toBe(false) + delete questions[0].blocking + expect(state.agent("wt-working")).toBe(false) + }) + it("does not keep a spinner for an offline session", () => { const state = busy({ working: { type: "offline" }, unknown: { type: "offline" } }) diff --git a/packages/kilo-vscode/tests/unit/worktree-activity.test.ts b/packages/kilo-vscode/tests/unit/worktree-activity.test.ts index cf482ca3d6..1a20083b76 100644 --- a/packages/kilo-vscode/tests/unit/worktree-activity.test.ts +++ b/packages/kilo-vscode/tests/unit/worktree-activity.test.ts @@ -4,7 +4,7 @@ import { createWorktreeActivity, WorktreeActivity } from "../../src/agent-manage type Snapshot = { statuses: Record permissions: Array<{ id: string; sessionID: string }> - questions: Array<{ id: string; sessionID: string }> + questions: Array<{ id: string; sessionID: string; blocking?: boolean }> } function defer() { @@ -137,6 +137,23 @@ describe("WorktreeActivity", () => { expect(test.posted.at(-1)).toEqual([]) }) + it("ignores non-blocking questions in snapshots and live events", async () => { + const test = setup(["/repo"], async () => ({ + ...snapshot({ child: "busy" }), + questions: [{ id: "note", sessionID: "child", blocking: false }], + })) + await test.activity.sync() + expect(test.posted.at(-1)).toEqual(["/repo"]) + + const question = asked("question", "live", "child") + test.activity.event({ ...question, properties: { ...question.properties, blocking: false } }, "/repo") + expect(test.posted.at(-1)).toEqual(["/repo"]) + test.activity.event(question, "/repo") + expect(test.posted.at(-1)).toEqual([]) + test.activity.event({ ...question, properties: { ...question.properties, blocking: false } }, "/repo") + expect(test.posted.at(-1)).toEqual(["/repo"]) + }) + it("clears sessions on completion, deletion, errors, and offline status", async () => { const test = setup(["/repo"], async () => snapshot()) await test.activity.sync() @@ -237,6 +254,8 @@ describe("WorktreeActivity", () => { dirs.length = 0 await test.activity.sync() expect(test.posted.at(-1)).toEqual([]) + test.activity.event(status("s1", "busy"), "/repo") + expect(test.posted.at(-1)).toEqual([]) gate.resolve(snapshot({ s1: "busy" })) await pending expect(test.posted.at(-1)).toEqual([]) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/project/session-busy.ts b/packages/kilo-vscode/webview-ui/agent-manager/project/session-busy.ts index 5c001a06b5..4809b583d4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/project/session-busy.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/project/session-busy.ts @@ -12,6 +12,7 @@ interface Status { interface Prompt { sessionID: string + blocking?: boolean } export function createSessionBusy(opts: { @@ -26,7 +27,11 @@ export function createSessionBusy(opts: { const any = (ids: string[]) => { if (ids.length === 0) return false const statuses = opts.statuses() - const blocked = new Set([...opts.permissions(), ...opts.questions()].map((item) => item.sessionID)) + const blocked = new Set( + [...opts.permissions(), ...opts.questions().filter((item) => item.blocking !== false)].map( + (item) => item.sessionID, + ), + ) return ids.some((id) => { const status = statuses[id] return (status?.type === "busy" || status?.type === "retry") && !blocked.has(id)