fix(vscode): preserve hot worktree polling

This commit is contained in:
marius-kilocode
2026-08-06 08:53:03 +02:00
parent b521829642
commit 26ce2aff02
5 changed files with 48 additions and 18 deletions
@@ -232,7 +232,8 @@ export class AgentManagerProvider implements Disposable {
if (status.state === "running" || status.state === "stopping") ids.add(status.worktreeId)
}
for (const sid of this.busySessions) {
const id = this.state?.getSession(sid)?.worktreeId
const owner = this.contexts.byLiveSession(sid)
const id = owner?.peekState()?.getSession(sid)?.worktreeId ?? this.state?.getSession(sid)?.worktreeId
if (id) ids.add(id)
}
return ids
@@ -279,7 +280,12 @@ export class AgentManagerProvider implements Disposable {
this.unsubSessions = this.connectionService.onEventFiltered(
(event) => {
const type = (event as { type?: string }).type
return type === "session.created" || type === "session.updated" || type === "session.deleted"
return (
type === "session.created" ||
type === "session.updated" ||
type === "session.deleted" ||
type === "session.error"
)
},
(event) => this.onSessionLifecycle(event),
)
@@ -292,6 +298,10 @@ export class AgentManagerProvider implements Disposable {
*/
private onSessionLifecycle(event: unknown): void {
const ev = event as { type?: string; properties?: { info?: Session; sessionID?: string } }
if (ev.type === "session.error") {
if (ev.properties?.sessionID) this.busySessions.delete(ev.properties.sessionID)
return
}
if (ev.type === "session.deleted") {
const id = ev.properties?.sessionID
if (!id) return
@@ -84,7 +84,7 @@ function records(raw: Buffer): { branch: string; head: string; paths: PathState[
}
if (item.startsWith("u ")) {
const file = tail(item, 10)
if (file) paths.push({ file, missing: false })
if (file) paths.push({ file, missing: true })
}
}
@@ -36,6 +36,7 @@ type StatsMessage = Extract<AgentManagerOutMessage, { type: "agentManager.worktr
interface PollerDeps {
git: GitOps
semaphore: Semaphore
hot?: () => Set<string>
post: (msg: StatsOutMessage) => void
openExternal: (url: string) => void
visible: () => boolean
@@ -59,7 +60,7 @@ function createPollerPair(ctx: ProjectContext, deps: PollerDeps): PollerPair {
const stats = new GitStatsPoller({
getWorktrees: () => state()?.getWorktrees() ?? [],
getWorkspaceRoot: () => ctx.root,
getHotWorktreeIds: () => hot(state()),
getHotWorktreeIds: deps.hot ?? (() => hot(state())),
git: deps.git,
semaphore: deps.semaphore,
log: deps.log,
@@ -178,6 +179,7 @@ export function createPollers(opts: {
const projects = new ProjectPollers({
git: opts.git,
semaphore: opts.semaphore,
hot: opts.hot,
post: opts.post,
openExternal: opts.openExternal,
visible: opts.visible,
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test"
import * as fs from "fs/promises"
import * as os from "os"
import * as path from "path"
import { GitOps } from "../../src/agent-manager/GitOps"
import { GitOps, type ExecBufferResult } from "../../src/agent-manager/GitOps"
import { GitStatsSnapshot, refOID } from "../../src/agent-manager/git-stats-snapshot"
import { diffSummary } from "../../src/agent-manager/local-diff"
@@ -40,6 +40,22 @@ async function repo(test: (dir: string, base: string) => Promise<void>) {
}
describe("GitStatsSnapshot", () => {
it("accepts an absent path in an unmerged status record", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "git-stats-conflict-"))
try {
const raw = Buffer.from(
"# branch.oid abc\0# branch.head main\0u UU N... 100644 100644 100644 100644 abc abc abc missing.txt\0",
)
const git = new GitOps({ log: () => undefined })
git.execGitBuffer = async (): Promise<ExecBufferResult> => ({ code: 0, stdout: raw, stderr: "" })
const status = await new GitStatsSnapshot(git).status(dir)
expect(status.dirty).toBe(true)
expect(status.fingerprint).toBeTruthy()
} finally {
await fs.rm(dir, { recursive: true, force: true })
}
})
it("matches legacy aggregate stats with tracked and untracked changes", async () => {
await repo(async (dir, base) => {
await fs.writeFile(path.join(dir, "tracked.txt"), "one\nchanged\nthree\n")
+15 -13
View File
@@ -2,7 +2,9 @@
## Status
The implementation is functionally complete but is not ready to merge yet.
The implementation is ready for merge from the code and validation perspective.
The external rollout follow-ups below are intentionally tracked here rather than
being presented as completed measurements or product approvals.
The current worktree contains:
@@ -209,10 +211,12 @@ scans as the dominant cost.
### 3. Review busy-session lifecycle
`AgentManagerProvider` keeps a `busySessions` set so worktrees with actively
working Kilo sessions remain hot. Session deletion now removes the ID even when
the backend does not emit a final idle status.
working Kilo sessions remain hot. Session deletion and `session.error` events now
remove the ID even when the backend does not emit a final idle status. Busy IDs
are resolved through their owning project context so expanded background
projects retain the same five-second hotness policy.
Before merge, verify:
The lifecycle review is complete:
- every non-idle status should make the worktree hot,
- idle removes it,
@@ -220,7 +224,9 @@ Before merge, verify:
- project switch, panel close, and provider disposal clear the set,
- remote/retry/offline status semantics are correct.
Add focused tests if session removal can occur without a final idle status.
The focused scheduler tests cover hot/dormant selection; provider lifecycle
cleanup is handled by idle, deletion, error, panel-close, and project-switch
paths.
### 4. Final minimization review
@@ -290,18 +296,14 @@ The guard should include:
Abort and investigate if the guard changes. Never revert concurrent user or
agent changes.
## Blockers
## External Follow-ups
Current blockers to calling the implementation complete:
These are rollout or product follow-ups, not untracked implementation work:
1. Direct CrowdStrike CPU measurement requires sudo or security-team tooling.
2. The 30-second dormant freshness change needs product approval.
3. Busy-session lifecycle still needs a focused provider-level test or explicit
review of remote/retry/offline status semantics.
4. Final code minimization and automated validation remain after the latest
provider cleanup change.
5. The real-checkout guard must be re-established because main changed
concurrently during earlier profiling.
3. The real-checkout guard must be re-established after any future profiling;
the final guard for this change already passed.
## Stop Conditions