mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
667 lines
29 KiB
TypeScript
667 lines
29 KiB
TypeScript
import * as vscode from "vscode"
|
|
import { KiloProvider } from "./KiloProvider"
|
|
import { AgentManagerProvider } from "./agent-manager/AgentManagerProvider"
|
|
import { VscodeHost } from "./agent-manager/vscode-host"
|
|
import { KiloClawProvider } from "./kiloclaw/KiloClawProvider"
|
|
import { DiffViewerProvider } from "./diff/DiffViewerProvider"
|
|
import { DiffSourceCatalog } from "./diff/sources/catalog"
|
|
import { DiffVirtualProvider } from "./DiffVirtualProvider"
|
|
import { SettingsEditorProvider } from "./SettingsEditorProvider"
|
|
import { MarketplacePanelProvider } from "./MarketplacePanelProvider"
|
|
import { MarketplaceNotifier } from "./services/marketplace/notifier"
|
|
import { SubAgentViewerProvider } from "./SubAgentViewerProvider"
|
|
import { EXTENSION_DISPLAY_NAME } from "./constants"
|
|
import { KiloConnectionService } from "./services/cli-backend"
|
|
import { registerAutocompleteProvider } from "./services/autocomplete"
|
|
import { ensureBackendForAutocomplete } from "./services/autocomplete/ensure-backend"
|
|
import { AutocompleteServiceManager } from "./services/autocomplete/AutocompleteServiceManager"
|
|
import { AttentionService } from "./services/attention"
|
|
import { BrowserAutomationService } from "./services/browser-automation"
|
|
import { TelemetryEventName, TelemetryProxy } from "./services/telemetry"
|
|
import { registerCommitMessageService } from "./services/commit-message"
|
|
import { registerCodeActions, registerTerminalActions, KiloCodeActionProvider } from "./services/code-actions"
|
|
import { registerToggleAutoApprove } from "./commands/toggle-auto-approve"
|
|
import { registerHeapSnapshot } from "./commands/heap-snapshot"
|
|
import { RemoteStatusService } from "./services/RemoteStatusService"
|
|
import { markWorkspace } from "./util/spotlight"
|
|
import { createNotebookBridge } from "./services/notebook"
|
|
|
|
let agentManager: AgentManagerProvider | undefined
|
|
let shuttingDown = false
|
|
|
|
const RESTORE_KEY = "kilo.workbench.restore"
|
|
|
|
type RestoreState = {
|
|
agentManager?: boolean
|
|
}
|
|
|
|
const panelTitleHandler = (panel: vscode.WebviewPanel) => (title: string) => {
|
|
panel.title = title || EXTENSION_DISPLAY_NAME
|
|
}
|
|
|
|
// Activated via "onStartupFinished" and "onUri" (package.json) so that commands, code actions,
|
|
// keybindings, autocomplete, commit-message generation, and URI deep links all work immediately —
|
|
// without requiring the user to open a Kilo sidebar or panel first. The CLI backend is NOT spawned here;
|
|
// it starts lazily when a webview connects or when ensureBackendForAutocomplete() triggers it.
|
|
export function activate(context: vscode.ExtensionContext) {
|
|
console.log("Kilo Code extension is now active")
|
|
shuttingDown = false
|
|
|
|
const telemetry = TelemetryProxy.getInstance()
|
|
|
|
// Create shared connection service (one server for all webviews)
|
|
const connectionService = new KiloConnectionService(context)
|
|
const notebookBridge = createNotebookBridge(connectionService)
|
|
let restore = context.workspaceState.get<RestoreState>(RESTORE_KEY) ?? {}
|
|
const remember = (patch: RestoreState) => {
|
|
const next = { ...restore, ...patch }
|
|
if (shuttingDown && patch.agentManager === false) next.agentManager = restore.agentManager
|
|
restore = next
|
|
void context.workspaceState.update(RESTORE_KEY, restore)
|
|
}
|
|
|
|
// Create browser automation service (manages Playwright MCP registration)
|
|
const browserAutomationService = new BrowserAutomationService(connectionService)
|
|
browserAutomationService.syncWithSettings()
|
|
|
|
// Create remote status service (one status bar item for all webviews)
|
|
const remoteService = new RemoteStatusService()
|
|
context.subscriptions.push(remoteService)
|
|
connectionService.setRemoteService(remoteService)
|
|
|
|
// Re-register browser automation MCP server on CLI backend reconnect, configure telemetry,
|
|
// set remote service client, and reload autocomplete so it picks up the now-available backend connection.
|
|
const unsubscribeStateChange = connectionService.onStateChange((state) => {
|
|
if (state === "connected") {
|
|
browserAutomationService.reregisterIfEnabled()
|
|
const config = connectionService.getServerConfig()
|
|
if (config) {
|
|
telemetry.configure(config.baseUrl, config.password)
|
|
// Sync the CLI's PostHog client with the current consent state. The
|
|
// CLI reads KILO_TELEMETRY_LEVEL once at spawn, so without this call
|
|
// a fresh CLI started while VS Code telemetry was off would stay
|
|
// opted out for the rest of the session.
|
|
telemetry.setEnabled(vscode.env.isTelemetryEnabled)
|
|
}
|
|
try {
|
|
remoteService.setClient(connectionService.getClient())
|
|
console.log("[Kilo New] CLI connected, calling remoteService.refresh()")
|
|
remoteService.refresh().catch((err) => console.warn("[Kilo New] initial remote refresh failed:", err))
|
|
} catch {
|
|
remoteService.setClient(null)
|
|
}
|
|
AutocompleteServiceManager.getInstance()?.load()
|
|
} else {
|
|
remoteService.clearState()
|
|
remoteService.setClient(null)
|
|
}
|
|
})
|
|
|
|
// Propagate runtime telemetry consent changes to the CLI subprocess so its
|
|
// PostHog client stays in sync with the user's VS Code telemetry setting.
|
|
context.subscriptions.push(
|
|
vscode.env.onDidChangeTelemetryEnabled((enabled) => {
|
|
telemetry.setEnabled(enabled)
|
|
}),
|
|
)
|
|
|
|
for (const folder of vscode.workspace.workspaceFolders ?? []) {
|
|
void markWorkspace(folder.uri.fsPath, (msg) => console.warn(`[Kilo New] ${msg}`))
|
|
}
|
|
|
|
// Track all open tab panel providers so toolbar button commands can target them.
|
|
// NOTE: The editor/title toolbar for tab panels intentionally omits Agent Manager
|
|
// and Marketplace buttons (unlike the sidebar). Too many icons causes VS Code to
|
|
// collapse them into a "..." overflow menu, hiding important buttons like Settings.
|
|
const tabPanels = new Map<vscode.WebviewPanel, KiloProvider>()
|
|
const activeTabProvider = () => {
|
|
for (const [panel, p] of tabPanels) {
|
|
if (panel.active) return p
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
// Create the provider with shared service
|
|
const provider = new KiloProvider(context.extensionUri, connectionService, context, {
|
|
focusContext: "kilo-code.new.sidebarFocused",
|
|
})
|
|
provider.setRemoteService(remoteService)
|
|
|
|
// Register the webview view provider for the sidebar.
|
|
// retainContextWhenHidden keeps the webview alive when switching to other sidebar panels.
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewViewProvider(KiloProvider.viewType, provider, {
|
|
webviewOptions: { retainContextWhenHidden: true },
|
|
}),
|
|
)
|
|
|
|
// Ensure Agent Manager navigation keybindings work when a VS Code terminal has focus.
|
|
// The terminal intercepts all keystrokes unless the command is listed in
|
|
// terminal.integrated.commandsToSkipShell, which only contains built-in
|
|
// commands by default.
|
|
const skip = ["kilo-code.new.agentManagerOpen", "kilo-code.new.agentManager.showTerminal"]
|
|
if (process.platform === "darwin") skip.push("kilo-code.new.agentManager.runScript")
|
|
ensureCommandsSkipShell(skip)
|
|
|
|
// Create KiloClaw chat provider for editor panel
|
|
const kiloClawProvider = new KiloClawProvider(context.extensionUri, connectionService)
|
|
context.subscriptions.push(kiloClawProvider)
|
|
|
|
// Create Agent Manager provider for editor panel
|
|
const agentManagerHost = new VscodeHost(context.extensionUri, connectionService, context, remoteService)
|
|
const agentManagerProvider = new AgentManagerProvider(agentManagerHost, connectionService)
|
|
agentManagerProvider.onPanelVisibilityChange((visible) => remember({ agentManager: visible }))
|
|
agentManager = agentManagerProvider
|
|
context.subscriptions.push(agentManagerProvider)
|
|
|
|
// Wire "Continue in Worktree" from sidebar → Agent Manager
|
|
provider.setContinueInWorktreeHandler((sessionId, progress) =>
|
|
agentManagerProvider.continueFromSidebar(sessionId, progress),
|
|
)
|
|
provider.setCreateWorktreeHandler((baseBranch, branchName) =>
|
|
agentManagerProvider.createFromSidebar(baseBranch, branchName),
|
|
)
|
|
|
|
// Register toggle auto-approve shortcut (Ctrl+Alt+A / Cmd+Alt+A)
|
|
const defaultDir = () => vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? process.cwd()
|
|
const autoApprove = registerToggleAutoApprove(
|
|
context,
|
|
connectionService,
|
|
(sessionId) => {
|
|
if (sessionId) {
|
|
const dir =
|
|
provider.getSessionDirectories().get(sessionId) ?? agentManagerProvider.getSessionDirectories().get(sessionId)
|
|
if (dir) return dir
|
|
}
|
|
return defaultDir()
|
|
},
|
|
() => {
|
|
const dirs = new Set([defaultDir()])
|
|
for (const dir of provider.getSessionDirectories().values()) dirs.add(dir)
|
|
for (const dir of agentManagerProvider.getSessionDirectories().values()) dirs.add(dir)
|
|
return [...dirs]
|
|
},
|
|
)
|
|
const attention = new AttentionService(connectionService, {
|
|
approve: (event, directory) => autoApprove.approve(event, directory),
|
|
})
|
|
|
|
// Prewarm only after all global event consumers are ready.
|
|
ensureBackendForAutocomplete(connectionService)
|
|
|
|
provider.setAutoApproveController(autoApprove)
|
|
agentManagerHost.setAutoApproveController(autoApprove)
|
|
|
|
// Register serializer so Agent Manager restores when VS Code restarts
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer(AgentManagerProvider.viewType, {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
if (restore.agentManager === false) {
|
|
panel.dispose()
|
|
return Promise.resolve()
|
|
}
|
|
const ctx = agentManagerHost.wrapExistingPanel(panel, {
|
|
onBeforeMessage: (msg) => agentManagerProvider.handleMessage(msg),
|
|
worktreeDirectories: () => agentManagerProvider.getWorktreeDirectories(),
|
|
})
|
|
agentManagerProvider.deserializePanel(ctx)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
// Register serializer so KiloClaw panel restores when VS Code restarts
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer(KiloClawProvider.viewType, {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
kiloClawProvider.restorePanel(panel)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
// Register serializer so "Open in Tab" restores when VS Code restarts
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer("kilo-code.new.TabPanel", {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
const tabProvider = new KiloProvider(context.extensionUri, connectionService, context, {
|
|
tabTitle: panelTitleHandler(panel),
|
|
})
|
|
tabProvider.setRemoteService(remoteService)
|
|
tabProvider.setAutoApproveController(autoApprove)
|
|
tabProvider.setContinueInWorktreeHandler((sessionId, progress) =>
|
|
agentManagerProvider.continueFromSidebar(sessionId, progress),
|
|
)
|
|
tabProvider.setCreateWorktreeHandler((baseBranch, branchName) =>
|
|
agentManagerProvider.createFromSidebar(baseBranch, branchName),
|
|
)
|
|
tabProvider.setDiffVirtualProvider(diffVirtualProvider)
|
|
tabProvider.resolveWebviewPanel(panel)
|
|
tabPanels.set(panel, tabProvider)
|
|
panel.onDidDispose(
|
|
() => {
|
|
console.log("[Kilo New] Tab panel restored from restart disposed")
|
|
tabPanels.delete(panel)
|
|
tabProvider.dispose()
|
|
},
|
|
null,
|
|
context.subscriptions,
|
|
)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
const diffSourceCatalog = new DiffSourceCatalog(connectionService)
|
|
context.subscriptions.push(diffSourceCatalog)
|
|
const diffViewerProvider = new DiffViewerProvider(context.extensionUri, connectionService, diffSourceCatalog, {
|
|
sessionIdProvider: () => provider.getCurrentSessionId(),
|
|
})
|
|
diffViewerProvider.setCommentHandler((comments, autoSend) => {
|
|
void provider.appendReviewComments(comments, autoSend)
|
|
})
|
|
context.subscriptions.push(diffViewerProvider)
|
|
|
|
// Create diff virtual provider (lightweight single-file diff for permission approval)
|
|
const diffVirtualProvider = new DiffVirtualProvider(context.extensionUri)
|
|
provider.setDiffVirtualProvider(diffVirtualProvider)
|
|
agentManagerHost.setDiffVirtualProvider(diffVirtualProvider)
|
|
context.subscriptions.push(diffVirtualProvider)
|
|
|
|
// Create standalone editor providers (open in editor area, not sidebar)
|
|
const settingsEditorProvider = new SettingsEditorProvider(context.extensionUri, connectionService, context)
|
|
settingsEditorProvider.setRemoteService(remoteService)
|
|
const marketplacePanelProvider = new MarketplacePanelProvider(context.extensionUri, connectionService, context)
|
|
context.subscriptions.push(settingsEditorProvider, marketplacePanelProvider)
|
|
|
|
// Surface a discardable notification when a marketplace item matches the workspace.
|
|
const marketplaceNotifier = new MarketplaceNotifier(connectionService, context, (item) =>
|
|
marketplacePanelProvider.openInstall(item),
|
|
)
|
|
context.subscriptions.push(marketplaceNotifier)
|
|
marketplaceNotifier.start()
|
|
|
|
// Create sub-agent viewer provider (read-only editor panel for sub-agent sessions)
|
|
const subAgentViewerProvider = new SubAgentViewerProvider(context.extensionUri, connectionService, context)
|
|
context.subscriptions.push(subAgentViewerProvider)
|
|
|
|
// Register serializers so standalone panels restore on restart
|
|
const settingsViews = ["settingsPanel", "profilePanel"] as const
|
|
for (const suffix of settingsViews) {
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer(`kilo-code.new.${suffix}`, {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
settingsEditorProvider.deserializePanel(panel)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
}
|
|
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer(MarketplacePanelProvider.viewType, {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
marketplacePanelProvider.deserializePanel(panel)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer(DiffViewerProvider.viewType, {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
diffViewerProvider.deserializePanel(panel)
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
context.subscriptions.push(
|
|
vscode.window.registerWebviewPanelSerializer("kilo-code.new.SubAgentViewerPanel", {
|
|
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
|
// Sub-agent viewer requires a session ID that can't be recovered
|
|
// after restart, so dispose the stale panel cleanly.
|
|
panel.dispose()
|
|
return Promise.resolve()
|
|
},
|
|
}),
|
|
)
|
|
|
|
// Sidebar menus use wrapper commands so this event measures real title button presses,
|
|
// not programmatic opens, shortcuts, or editor title commands.
|
|
const track = (button: string, command: string) => {
|
|
TelemetryProxy.capture(TelemetryEventName.TITLE_BUTTON_CLICKED, {
|
|
button,
|
|
surface: "sidebar_title",
|
|
})
|
|
void vscode.commands.executeCommand(command)
|
|
}
|
|
|
|
// Register toolbar button command handlers
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.plusButtonClicked", () => {
|
|
track("new_task", "kilo-code.new.plusButtonClicked")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.historyButtonClicked", () => {
|
|
track("history", "kilo-code.new.historyButtonClicked")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.agentManagerOpen", () => {
|
|
track("agent_manager", "kilo-code.new.agentManagerOpen")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.kiloClawOpen", () => {
|
|
track("kiloclaw", "kilo-code.new.kiloClawOpen")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.marketplaceButtonClicked", () => {
|
|
track("marketplace", "kilo-code.new.marketplaceButtonClicked")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.profileButtonClicked", () => {
|
|
track("profile", "kilo-code.new.profileButtonClicked")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.settingsButtonClicked", () => {
|
|
track("settings", "kilo-code.new.settingsButtonClicked")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => {
|
|
const tab = activeTabProvider()
|
|
if (tab) tab.postMessage({ type: "action", action: "plusButtonClicked" })
|
|
else provider.postMessage({ type: "action", action: "plusButtonClicked" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManagerOpen", () => {
|
|
agentManagerProvider.openPanel()
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.marketplaceButtonClicked", (directory?: string | null) => {
|
|
marketplacePanelProvider.openPanel(directory)
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.kiloClawOpen", () => {
|
|
kiloClawProvider.openPanel()
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.historyButtonClicked", () => {
|
|
const tab = activeTabProvider()
|
|
if (tab) tab.postMessage({ type: "action", action: "historyButtonClicked" })
|
|
else provider.postMessage({ type: "action", action: "historyButtonClicked" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.cycleAgentMode", () => {
|
|
const tab = activeTabProvider()
|
|
if (tab) tab.postMessage({ type: "action", action: "cycleAgentMode" })
|
|
else provider.postMessage({ type: "action", action: "cycleAgentMode" })
|
|
agentManagerProvider.postMessage({ type: "action", action: "cycleAgentMode" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.cyclePreviousAgentMode", () => {
|
|
const tab = activeTabProvider()
|
|
if (tab) tab.postMessage({ type: "action", action: "cyclePreviousAgentMode" })
|
|
else provider.postMessage({ type: "action", action: "cyclePreviousAgentMode" })
|
|
agentManagerProvider.postMessage({ type: "action", action: "cyclePreviousAgentMode" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.profileButtonClicked", () => {
|
|
settingsEditorProvider.openPanel("profile")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.settingsButtonClicked", (tab?: string) => {
|
|
settingsEditorProvider.openPanel("settings", tab)
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.openIndexingSettings", () => {
|
|
settingsEditorProvider.openPanel("settings", "indexing")
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.showMemory", async () => {
|
|
if (agentManagerProvider.isActive()) {
|
|
await agentManagerProvider.showMemory()
|
|
return
|
|
}
|
|
const target = activeTabProvider() ?? provider
|
|
if (target === provider) await vscode.commands.executeCommand("kilo-code.SidebarProvider.focus")
|
|
await target.waitForReady()
|
|
await target.showMemory()
|
|
}),
|
|
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, source: "legacy" })
|
|
}),
|
|
// legacy-migration end
|
|
vscode.commands.registerCommand("kilo-code.new.generateTerminalCommand", async () => {
|
|
const input = await vscode.window.showInputBox({
|
|
prompt: "Describe the terminal command you want to generate",
|
|
placeHolder: "e.g., find all .ts files modified in the last 24 hours",
|
|
})
|
|
if (!input) return
|
|
await vscode.commands.executeCommand("kilo-code.SidebarProvider.focus")
|
|
await provider.waitForReady()
|
|
provider.postMessage({ type: "triggerTask", text: `Generate a terminal command: ${input}` })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.toggleRemote", () => {
|
|
remoteService.toggle().catch((err) => console.error("[Kilo New] toggleRemote command failed:", err))
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.openInTab", () => {
|
|
return openKiloInNewTab(
|
|
context,
|
|
connectionService,
|
|
agentManagerProvider,
|
|
tabPanels,
|
|
diffVirtualProvider,
|
|
remoteService,
|
|
autoApprove,
|
|
)
|
|
}),
|
|
vscode.commands.registerCommand(
|
|
"kilo-code.new.showChanges",
|
|
(arg?: { sessionId?: string; turnId?: string; initialSourceId?: string }) => {
|
|
diffViewerProvider.openFromCommand(arg)
|
|
},
|
|
),
|
|
vscode.commands.registerCommand("kilo-code.new.openSubAgentViewer", (sessionID: string, title?: string) => {
|
|
subAgentViewerProvider.openPanel(sessionID, title)
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.previousSession", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "sessionPrevious" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.nextSession", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "sessionNext" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.previousTab", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "tabPrevious" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.nextTab", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "tabNext" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.search", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "search" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.showTerminal", () => {
|
|
// Route through the webview so it can reach into the active session
|
|
// state and open the VS Code integrated terminal for it.
|
|
agentManagerProvider.postMessage({ type: "action", action: "showTerminal" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.runScript", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "runScript" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.toggleDiff", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "toggleDiff" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.showShortcuts", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "showShortcuts" })
|
|
}),
|
|
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.newTab", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "newTab" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.newTerminal", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "newTerminal" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.closeTab", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "closeTab" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.newWorktree", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "newWorktree" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.quickWorktree", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "quickWorktree" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.openWorktree", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "openWorktree" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.openPR", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "openPR" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.closeWorktree", () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: "closeWorktree" })
|
|
}),
|
|
vscode.commands.registerCommand("kilo-code.new.agentManager.advancedWorktree", () =>
|
|
agentManagerProvider.openAdvancedWorktree(),
|
|
),
|
|
...Array.from({ length: 9 }, (_, i) =>
|
|
vscode.commands.registerCommand(`kilo-code.new.agentManager.jumpTo${i + 1}`, () => {
|
|
agentManagerProvider.postMessage({ type: "action", action: `jumpTo${i + 1}` })
|
|
}),
|
|
),
|
|
)
|
|
|
|
// Register URI handler for extension deep links (vscode://kilocode.kilo-code/kilocode/...)
|
|
context.subscriptions.push(
|
|
vscode.window.registerUriHandler({
|
|
async handleUri(uri: vscode.Uri) {
|
|
const sessionMatch = uri.path.match(/^\/kilocode\/s\/([a-zA-Z0-9_-]+)$/)
|
|
const sessionId = sessionMatch?.[1]
|
|
if (sessionId) {
|
|
console.log("[Kilo New] URI handler: opening cloud session:", sessionId)
|
|
await vscode.commands.executeCommand(`${KiloProvider.viewType}.focus`)
|
|
provider.openCloudSession(sessionId)
|
|
return
|
|
}
|
|
|
|
if (uri.path !== "/kilocode/switch" && uri.path !== "/kilocode/model") return
|
|
const params = new URLSearchParams(uri.query)
|
|
const modelID = params.get("model") || undefined
|
|
const agent = params.get("agent") || undefined
|
|
if (!modelID && !agent) return
|
|
console.log("[Kilo New] URI handler: applying linked Kilo selection:", { modelID, agent })
|
|
await vscode.commands.executeCommand(`${KiloProvider.viewType}.focus`)
|
|
provider.selectKiloModel(modelID, agent)
|
|
},
|
|
}),
|
|
)
|
|
|
|
// Register autocomplete provider
|
|
void registerAutocompleteProvider(context, connectionService)
|
|
|
|
// Register commit message generation
|
|
registerCommitMessageService(context, connectionService)
|
|
|
|
registerHeapSnapshot(context, connectionService)
|
|
|
|
context.subscriptions.push(
|
|
vscode.commands.registerCommand("kilo-code.new.reload", () => {
|
|
provider.reload().catch((e) => console.error("[Kilo New] reload command failed:", e))
|
|
}),
|
|
)
|
|
|
|
// Register code actions (editor context menus, terminal context menus, keyboard shortcuts)
|
|
registerCodeActions(context, provider, agentManagerProvider, activeTabProvider)
|
|
registerTerminalActions(context, provider, agentManagerProvider)
|
|
|
|
// Register CodeActionProvider (lightbulb quick fixes)
|
|
context.subscriptions.push(
|
|
vscode.languages.registerCodeActionsProvider(
|
|
{ scheme: "file" },
|
|
new KiloCodeActionProvider(),
|
|
KiloCodeActionProvider.metadata,
|
|
),
|
|
)
|
|
|
|
// Dispose services when extension deactivates (kills the server)
|
|
context.subscriptions.push({
|
|
dispose: () => {
|
|
shuttingDown = true
|
|
unsubscribeStateChange()
|
|
attention.dispose()
|
|
browserAutomationService.dispose()
|
|
provider.dispose()
|
|
notebookBridge.dispose()
|
|
connectionService.dispose()
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function deactivate() {
|
|
shuttingDown = true
|
|
await agentManager?.shutdown()
|
|
TelemetryProxy.getInstance().shutdown()
|
|
}
|
|
|
|
function openKiloInNewTab(
|
|
context: vscode.ExtensionContext,
|
|
connectionService: KiloConnectionService,
|
|
agentManagerProvider: AgentManagerProvider,
|
|
tabPanels: Map<vscode.WebviewPanel, KiloProvider>,
|
|
diffVirtualProvider: DiffVirtualProvider,
|
|
remoteService: RemoteStatusService,
|
|
autoApprove: ReturnType<typeof registerToggleAutoApprove>,
|
|
) {
|
|
const panel = vscode.window.createWebviewPanel(
|
|
"kilo-code.new.TabPanel",
|
|
EXTENSION_DISPLAY_NAME,
|
|
vscode.ViewColumn.Active,
|
|
{
|
|
enableScripts: true,
|
|
retainContextWhenHidden: true,
|
|
localResourceRoots: [context.extensionUri],
|
|
},
|
|
)
|
|
|
|
panel.iconPath = {
|
|
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "kilo-light.svg"),
|
|
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "kilo-dark.svg"),
|
|
}
|
|
|
|
const tabProvider = new KiloProvider(context.extensionUri, connectionService, context, {
|
|
tabTitle: panelTitleHandler(panel),
|
|
})
|
|
tabProvider.setRemoteService(remoteService)
|
|
tabProvider.setAutoApproveController(autoApprove)
|
|
tabProvider.setContinueInWorktreeHandler((sessionId, progress) =>
|
|
agentManagerProvider.continueFromSidebar(sessionId, progress),
|
|
)
|
|
tabProvider.setCreateWorktreeHandler((baseBranch, branchName) =>
|
|
agentManagerProvider.createFromSidebar(baseBranch, branchName),
|
|
)
|
|
tabProvider.setDiffVirtualProvider(diffVirtualProvider)
|
|
tabProvider.resolveWebviewPanel(panel)
|
|
tabPanels.set(panel, tabProvider)
|
|
|
|
panel.onDidDispose(
|
|
() => {
|
|
console.log("[Kilo New] Tab panel disposed")
|
|
tabPanels.delete(panel)
|
|
tabProvider.dispose()
|
|
},
|
|
null,
|
|
context.subscriptions,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Add extension commands to terminal.integrated.commandsToSkipShell so they
|
|
* work when a VS Code terminal has focus. The setting only ships with built-in
|
|
* commands; extension commands must be added explicitly.
|
|
*/
|
|
function ensureCommandsSkipShell(commands: string[]): void {
|
|
const config = vscode.workspace.getConfiguration("terminal.integrated")
|
|
const info = config.inspect<string[]>("commandsToSkipShell")
|
|
// Update whichever scope already carries an override so we don't
|
|
// shadow workspace settings or leak workspace values into global.
|
|
const [existing, target] = info?.workspaceFolderValue
|
|
? [info.workspaceFolderValue, vscode.ConfigurationTarget.WorkspaceFolder]
|
|
: info?.workspaceValue
|
|
? [info.workspaceValue, vscode.ConfigurationTarget.Workspace]
|
|
: [info?.globalValue ?? [], vscode.ConfigurationTarget.Global]
|
|
const missing = commands.filter((cmd) => !existing.includes(cmd))
|
|
if (missing.length === 0) return
|
|
config.update("commandsToSkipShell", [...existing, ...missing], target)
|
|
}
|