fix(agent-manager): handle activity review edge cases

This commit is contained in:
marius-kilocode
2026-08-27 11:45:56 +02:00
parent 094ca1987e
commit 5a685dd045
5 changed files with 44 additions and 4 deletions
@@ -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(),
@@ -4,7 +4,7 @@ import type { KiloConnectionService } from "../services/cli-backend"
type Snapshot = {
statuses: Record<string, { type: string }>
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 }
@@ -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" } })
@@ -4,7 +4,7 @@ import { createWorktreeActivity, WorktreeActivity } from "../../src/agent-manage
type Snapshot = {
statuses: Record<string, { type: string }>
permissions: Array<{ id: string; sessionID: string }>
questions: Array<{ id: string; sessionID: string }>
questions: Array<{ id: string; sessionID: string; blocking?: boolean }>
}
function defer<T>() {
@@ -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([])
@@ -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)