refactor(vscode): extract remote status and focus helpers in KiloProvider

Consolidate remote message handling into RemoteStatusService.handleMessage(),
extract sendRemoteStatus/focusSession helpers to reduce duplication, and move
trackOpenSessions effect into its own module. Add runtime guard for undefined
enabled parameter in setRemoteEnabled handler.
This commit is contained in:
Evgeny Shurakov
2026-04-09 14:08:16 +02:00
parent 684319891e
commit 403bcc0ec4
4 changed files with 60 additions and 59 deletions
+23 -51
View File
@@ -217,9 +217,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
setRemoteService(service: RemoteStatusService): void {
this.remoteService = service
this.unsubscribeRemote = service.onChange((state) => {
this.postMessage({ type: "remoteStatus", enabled: state.enabled, connected: state.connected })
})
this.unsubscribeRemote = service.onChange(() => this.sendRemoteStatus())
}
private sendRemoteStatus(): void {
const s = this.remoteService?.getState()
if (s) this.postMessage({ type: "remoteStatus", enabled: s.enabled, connected: s.connected })
}
private focusSession(id?: string): void {
if (id) this.connectionService.registerFocused(this.instanceId, id)
else this.connectionService.unregisterFocused(this.instanceId)
}
public setProjectDirectory(directory: string | null): void {
@@ -336,11 +342,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const reconcile = this.sessionStatusMap.size === 0
void this.seedSessionStatusMap(reconcile)
// Send current remote status so settings panel shows correct state
if (this.remoteService) {
const state = this.remoteService.getState()
this.postMessage({ type: "remoteStatus", enabled: state.enabled, connected: state.connected })
}
this.sendRemoteStatus()
}
// legacy-migration start
@@ -382,11 +384,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
webviewView.onDidChangeVisibility(() => {
vscode.commands.executeCommand("setContext", "kilo-code.new.sidebarVisible", webviewView.visible)
this.statsPoller?.setEnabled(webviewView.visible)
if (webviewView.visible) {
if (this.currentSession) this.connectionService.registerFocused(this.instanceId, this.currentSession.id)
} else {
this.connectionService.unregisterFocused(this.instanceId)
}
this.focusSession(webviewView.visible ? this.currentSession?.id : undefined)
})
// Initialize connection to CLI backend
@@ -412,13 +410,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.setupWebviewMessageHandler(panel.webview)
// Unregister focus when the editor tab becomes inactive, re-register when active
panel.onDidChangeViewState(() => {
if (panel.active) {
if (this.currentSession) this.connectionService.registerFocused(this.instanceId, this.currentSession.id)
} else {
this.connectionService.unregisterFocused(this.instanceId)
}
})
panel.onDidChangeViewState(() => this.focusSession(panel.active ? this.currentSession?.id : undefined))
this.initializeConnection()
}
@@ -613,7 +605,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "clearSession":
this.contextSessionID = this.currentSession?.id ?? this.contextSessionID
this.currentSession = null
this.connectionService.unregisterFocused(this.instanceId)
this.focusSession()
break
case "loadMessages":
// Don't await: allow parallel loads so rapid session switching
@@ -850,29 +842,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
await this.handleRenameSession(message.sessionID, message.title)
break
case "toggleRemote":
this.remoteService?.toggle().catch((err) => {
console.error("[Kilo New] toggleRemote failed:", err)
this.postMessage({ type: "error", message: "Failed to toggle remote control" })
})
break
case "setRemoteEnabled":
this.remoteService?.setEnabled(message.enabled).catch((err) => {
console.error("[Kilo New] setRemoteEnabled failed:", err)
this.postMessage({
type: "error",
message: `Failed to ${message.enabled ? "enable" : "disable"} remote control`,
case "requestRemoteStatus":
this.remoteService
?.handleMessage(message.type, message.enabled)
.then((s) => {
if (s) this.sendRemoteStatus()
})
})
.catch((err) => console.error("[Kilo New] remote message failed:", err))
break
case "requestRemoteStatus": {
// Send cached state immediately, then refresh
if (this.remoteService) {
const state = this.remoteService.getState()
this.postMessage({ type: "remoteStatus", enabled: state.enabled, connected: state.connected })
}
this.remoteService?.refresh().catch((err) => console.error("[Kilo New] remoteStatus refresh failed:", err))
break
}
case "updateSetting":
await this.handleUpdateSetting(message.key, message.value)
break
@@ -1105,10 +1083,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.unsubscribeEvent = this.connectionService.onEventFiltered(
(event) => {
// Remote status events are global and should always pass through
if (event.type === "kilo-sessions.remote-status-changed") {
return true
}
if (event.type === "kilo-sessions.remote-status-changed") return true
const sessionId = this.connectionService.resolveEventSessionId(event)
// message.part.updated and message.part.delta are always session-scoped; drop if session unknown.
@@ -1305,7 +1280,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
private async handleLoadMessages(sessionID: string): Promise<void> {
// Track the session so we receive its SSE events
this.trackedSessionIds.add(sessionID)
this.connectionService.registerFocused(this.instanceId, sessionID)
this.focusSession(sessionID)
this.contextSessionID = sessionID
if (!this.client) {
@@ -2885,10 +2860,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
*/
private handleEvent(event: Event): void {
if (event.type === "kilo-sessions.remote-status-changed") {
this.remoteService?.updateFromEvent({
enabled: event.properties.enabled,
connected: event.properties.connected,
})
this.remoteService?.updateFromEvent({ enabled: event.properties.enabled, connected: event.properties.connected })
return
}
@@ -3304,7 +3276,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
*/
dispose(): void {
this.unsubscribeRemote?.()
this.connectionService.unregisterFocused(this.instanceId)
this.focusSession()
this.statsPoller?.stop()
this.statsGitOps?.dispose()
this.unsubscribeEvent?.()
@@ -74,6 +74,26 @@ export class RemoteStatusService implements vscode.Disposable {
this.update({ enabled, connected: false })
}
/**
* Handle a remote-related webview message.
* Returns a response message to post back to the webview, or null.
*/
async handleMessage(type: string, enabled?: boolean): Promise<RemoteState | null> {
switch (type) {
case "toggleRemote":
await this.toggle()
return null
case "setRemoteEnabled":
if (enabled === undefined) return null
await this.setEnabled(enabled)
return null
case "requestRemoteStatus":
void this.refresh()
return this.state
}
return null
}
dispose(): void {
this.listeners.clear()
this.bar.dispose()
@@ -97,6 +97,7 @@ import { buildTopLevelItems, isGrouped, isGroupStart, isGroupEnd, type TopLevelI
import { sectionAwareDetector } from "./section-dnd"
import { ConstrainDragXAxis } from "./constrain-drag-x"
import { mergeWorktreeDiffs } from "./diff-state"
import { trackOpenSessions } from "./open-sessions"
import "./agent-manager.css"
import "./agent-manager-review.css"
@@ -667,14 +668,7 @@ const AgentManagerContent: Component = () => {
setLocalSessionIDs(valid)
}
})
// Report all open (non-pending) session IDs to extension for heartbeat
createEffect(() => {
const local = localSessionIDs().filter((id) => !isPending(id))
const managed = managedSessions().map((ms) => ms.id)
const all = [...new Set([...local, ...managed])]
vscode.postMessage({ type: "agentManager.openSessions", sessionIDs: all })
})
trackOpenSessions(localSessionIDs, isPending, managedSessions, vscode.postMessage)
// Drop in-memory review state for worktrees that no longer exist.
createEffect(() => {
@@ -0,0 +1,15 @@
import { createEffect } from "solid-js"
import type { Accessor } from "solid-js"
/** Reactive effect: reports open (non-pending) session IDs to the extension for heartbeat. */
export function trackOpenSessions(
local: Accessor<string[]>,
pending: (id: string) => boolean,
managed: Accessor<Array<{ id: string }>>,
post: (msg: { type: "agentManager.openSessions"; sessionIDs: string[] }) => void,
): void {
createEffect(() => {
const ids = [...new Set([...local().filter((id) => !pending(id)), ...managed().map((s) => s.id)])]
post({ type: "agentManager.openSessions", sessionIDs: ids })
})
}