mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(vscode): preserve hot worktree polling
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user