Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kilo-memory-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Add project memory controls, status, and activity details to the VS Code extension.
11 changes: 11 additions & 0 deletions packages/kilo-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@
"title": "Migrate Settings from Legacy Version",
"category": "Kilo Code"
},
{
"command": "kilo-code.new.showMemory",
"title": "Show Memory",
"category": "Kilo Code"
},
{
"command": "kilo-code.new.toggleMemory",
"title": "Toggle Project Memory",
"category": "Kilo Code"
},
{
"command": "kilo-code.new.autocomplete.generateSuggestions",
"title": "Generate Suggested Edits",
Expand Down Expand Up @@ -1082,6 +1092,7 @@
"@kilocode/kilo-gateway": "workspace:*",
"@kilocode/kilo-i18n": "workspace:*",
"@kilocode/kilo-indexing": "workspace:*",
"@kilocode/kilo-memory": "workspace:*",
"@kilocode/kilo-ui": "workspace:*",
"@kilocode/sdk": "workspace:*",
"@opencode-ai/ui": "workspace:*",
Expand Down
73 changes: 72 additions & 1 deletion packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import {
import { fetchAndSendPendingSuggestions } from "./kilo-provider/handlers/suggestion"
import { nativeTitle } from "./kilo-provider/native-tab-title"
import { parseReview, reviewMetadata, type ReviewMessageData } from "./shared/review-comments"
import { KiloProviderMemory } from "./kilo-provider/memory"

import {
buildActionContext,
Expand Down Expand Up @@ -296,6 +297,12 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
private readonly streams = new SessionStreamScheduler((msg) => this.postMessage(msg))
private readonly visibleTaskStreams = new VisibleTaskStreams((id, visible) => this.streams.setVisible(id, visible))
private readonly confirmations = new MessageConfirmation()
private readonly memory = new KiloProviderMemory({
client: () => this.client ?? undefined,
session: () => this.currentSession ?? undefined,
dir: (sessionID) => this.getProjectDirectory(sessionID) ?? this.getWorkspaceDirectory(sessionID),
post: (message) => this.postMessage(message),
})
private unsubscribeEvent: (() => void) | null = null
private unsubscribeState: (() => void) | null = null
/** Cached legacy migration data so migrate() doesn't re-read from disk/SecretStorage. */ // legacy-migration
Expand Down Expand Up @@ -351,7 +358,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
) {
this.projectDirectory = opts.projectDirectory
this.slimEditMetadata = opts.slimEditMetadata ?? true

TelemetryProxy.getInstance().setProvider(this)
}

Expand Down Expand Up @@ -776,6 +782,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
return
}
this.visibleTaskStreams.handle(message)
if (await this.handleMemoryMessage(message)) return
switch (message.type) {
case "webviewReady":
console.log("[Kilo New] KiloProvider: ✅ webviewReady received")
Expand Down Expand Up @@ -1281,6 +1288,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper

// Remote status events are global and should always pass through
if (event.type === "kilo-sessions.remote-status-changed") return true
if (event.type === "memory.status" || event.type === "memory.updated" || event.type === "memory.error")
return true
const sessionId = this.resolveEventSessionId(event)

// message.part.* events are always session-scoped; drop if session unknown.
Expand Down Expand Up @@ -1409,6 +1418,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.fetchAndSendConfig(),
this.fetchAndSendIndexingStatus(),
this.fetchAndSendNotifications(),
this.memory.fetch(),
this.seedSessionStatusMap(),
])
this.cachedGitRepo = await hasGit(this.client!, this.getWorkspaceDirectory())
Expand Down Expand Up @@ -2069,6 +2079,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}

private async handleMemoryMessage(message: Record<string, unknown>): Promise<boolean> {
return this.memory.handle(message)
}

/**
* Fetch backend config and send to webview.
*/
Expand Down Expand Up @@ -3099,6 +3113,38 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
return
}

if (event.type === "memory.status" || event.type === "memory.updated" || event.type === "memory.error") {
const props = event.properties as { sessionID?: unknown; detail?: unknown; reason?: unknown }
const eventSessionID = typeof props.sessionID === "string" ? props.sessionID : undefined
const active = this.currentSession?.id
const local =
!directory || sameDirectory(directory, this.getProjectDirectory(active) ?? this.getWorkspaceDirectory(active))
const tracked = Boolean(eventSessionID && this.trackedSessionIds.has(eventSessionID))
if (!local && !tracked) return
if (tracked && eventSessionID && directory) this.trackDirectory(eventSessionID, directory)
const targets = new Set<string | undefined>()
if (tracked && eventSessionID) targets.add(eventSessionID)
if (local && active) targets.add(active)
if (targets.size === 0 && local) targets.add(undefined)
const detail =
props.detail && typeof props.detail === "object"
? props.detail
: event.type === "memory.error" && typeof props.reason === "string"
? { type: "error", message: props.reason, reason: props.reason }
: undefined
for (const sessionID of targets) {
if (detail) {
this.postMessage({
type: "memoryEvent",
sessionID,
detail,
})
}
void this.memory.fetch(sessionID, false)
}
return
}

// Drop session events from other projects before any tracking logic.
// This must come first: the trackedSessionIds guard below would otherwise
// let a foreign session through if it was accidentally tracked.
Expand Down Expand Up @@ -3281,6 +3327,31 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.flushPendingReviewComments()
}

public async inspectMemory(sessionID?: string): Promise<void> {
await this.memory.inspect(sessionID ?? this.currentSession?.id)
}

public async toggleMemory(sessionID?: string): Promise<void> {
if (!this.client) {
void vscode.window.showErrorMessage("Not connected to CLI backend")
return
}

const sid = sessionID ?? this.currentSession?.id
const directory = this.getProjectDirectory(sid) ?? this.getWorkspaceDirectory(sid)
try {
const { data: status } = await this.client.memory.status({ directory }, { throwOnError: true })
const operation = status.state.enabled ? "disable" : "enable"
const ok = await this.memory.run({ operation, sessionID: sid })
if (ok) {
void vscode.window.showInformationMessage(`Project memory ${operation === "enable" ? "enabled" : "disabled"}.`)
}
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to toggle memory:", error)
void vscode.window.showErrorMessage(getErrorMessage(error) || "Failed to toggle memory")
}
}

private flushPendingReviewComments(): void {
if (!this.webview || !this.isWebviewReady || this.pendingReviewComments.length === 0) return

Expand Down
52 changes: 49 additions & 3 deletions packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,52 @@ export class AgentManagerProvider implements Disposable {
return this.panel?.active === true
}

private async waitForPanel(panel: PanelContext, promise: Promise<void>): Promise<boolean> {
const done = promise.then(() => true)
let sub: Disposable | undefined
const disposed = new Promise<false>((resolve) => {
sub = panel.onDidDispose(() => {
sub?.dispose()
resolve(false)
})
})
void done.finally(() => sub?.dispose())
const ok = await Promise.race([done, disposed])
return ok && this.panel === panel
}

private waitForPanelReady(panel: PanelContext): Promise<boolean> {
return this.waitForPanel(panel, panel.waitForReady())
}

private waitForPanelActive(panel: PanelContext): Promise<boolean> {
return this.waitForPanel(panel, panel.waitForActive())
}

public async inspectMemory(): Promise<void> {
const panel = this.panel
const sid = this.activeSessionId
if (!panel || !sid) {
this.host.showError("No active Agent Manager session")
return
}
if (!(await this.waitForPanelReady(panel))) return
if (this.activeSessionId !== sid) return
await panel.sessions.inspectMemory(sid)
}

public async toggleMemory(): Promise<void> {
const panel = this.panel
const sid = this.activeSessionId
if (!panel || !sid) {
this.host.showError("No active Agent Manager session")
return
}
if (!(await this.waitForPanelReady(panel))) return
if (this.activeSessionId !== sid) return
await panel.sessions.toggleMemory(sid)
}

/** Expose worktree session→directory mappings for the auto-approve toggle. */
public getSessionDirectories(): ReadonlyMap<string, string> {
return this.panel?.sessions.getSessionDirectories() ?? new Map()
Expand Down Expand Up @@ -1762,7 +1808,7 @@ export class AgentManagerProvider implements Disposable {
this.openPanel()
const panel = this.panel
if (!panel) return
await panel.waitForReady()
if (!(await this.waitForPanelReady(panel))) return
await this.waitForStateReady("createFromSidebar")
await this.onCreateWorktree(baseBranch, branchName)
}
Expand All @@ -1771,8 +1817,8 @@ export class AgentManagerProvider implements Disposable {
this.openPanel()
const panel = this.panel
if (!panel) return
await panel.waitForActive()
await panel.waitForReady()
if (!(await this.waitForPanelActive(panel))) return
if (!(await this.waitForPanelReady(panel))) return
await this.waitForStateReady("openAdvancedWorktree")
queueMicrotask(() => this.postToWebview({ type: "action", action: "advancedWorktree" }))
}
Expand Down
2 changes: 2 additions & 0 deletions packages/kilo-vscode/src/agent-manager/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export interface SessionProvider {
* The callback receives the new session and its directory so the Agent Manager
* can route it to the correct worktree instead of LOCAL. */
onFollowupAdopted(cb: (session: Session, directory: string) => void): void
inspectMemory(sessionID?: string): Promise<void>
toggleMemory(sessionID?: string): Promise<void>
dispose(): void
}

Expand Down
2 changes: 2 additions & 0 deletions packages/kilo-vscode/src/agent-manager/vscode-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export class VscodeHost implements Host {
registerSession: (s) => provider.registerSession(s),
recoverPendingPrompts: () => provider.recoverPendingPrompts(),
onFollowupAdopted: (cb) => provider.onFollowupAdopted(cb),
inspectMemory: (id) => provider.inspectMemory(id),
toggleMemory: (id) => provider.toggleMemory(id),
dispose: () => provider.dispose(),
}

Expand Down
20 changes: 20 additions & 0 deletions packages/kilo-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,26 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("kilo-code.new.openIndexingSettings", () => {
settingsEditorProvider.openPanel("settings", "indexing")
}),
vscode.commands.registerCommand("kilo-code.new.showMemory", async () => {
if (agentManagerProvider.isActive()) {
await agentManagerProvider.inspectMemory()
return
}
const target = activeTabProvider() ?? provider
Comment thread
johnnyeric marked this conversation as resolved.
if (target === provider) await vscode.commands.executeCommand("kilo-code.SidebarProvider.focus")
await target.waitForReady()
await target.inspectMemory()
}),
vscode.commands.registerCommand("kilo-code.new.toggleMemory", async () => {
if (agentManagerProvider.isActive()) {
await agentManagerProvider.toggleMemory()
return
}
const target = activeTabProvider() ?? provider
if (target === provider) await vscode.commands.executeCommand("kilo-code.SidebarProvider.focus")
await target.waitForReady()
await target.toggleMemory()
}),
// legacy-migration start
vscode.commands.registerCommand("kilo-code.new.openMigrationWizard", () => {
provider.postMessage({ type: "migrationState", needed: true })
Expand Down
Loading
Loading