Merge pull request #11645 from Kilo-Org/like-drawer

fix(vscode): use native sandbox notifications
This commit is contained in:
Marius
2026-06-25 10:16:44 +02:00
committed by GitHub
6 changed files with 49 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Show sandbox state changes as concise VS Code notifications.
+1
View File
@@ -2597,6 +2597,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
...data,
requestID: input.requestID,
})
vscode.window.showInformationMessage(data.enabled ? "Sandbox enabled" : "Sandbox disabled")
} catch (error) {
if (this.connectionState === "connected" && this.connectionGeneration === generation && this.client === client) {
this.postSandboxError(resolved.sid, error, revision, input.requestID)
@@ -79,6 +79,7 @@ const mockVscode = {
visibleNotebookEditors: [],
tabGroups: { all: [] },
showTextDocument: async () => {},
showInformationMessage: async () => undefined,
showWarningMessage: async () => undefined,
createTerminal: () => ({ show: noop, sendText: noop, dispose: noop }),
createOutputChannel: () => ({
@@ -1,4 +1,5 @@
import { describe, it, expect, spyOn } from "bun:test"
import * as vscode from "vscode"
import type { PartUpdate } from "../../src/shared/stream-messages"
// vscode mock is provided by the shared preload (tests/setup/vscode-mock.ts)
@@ -292,6 +293,7 @@ describe("KiloProvider sandbox status", () => {
describe("KiloProvider sandbox toggle", () => {
it("creates a session before toggling from the empty composer", async () => {
const notice = spyOn(vscode.window, "showInformationMessage").mockResolvedValue(undefined)
const client = createClient()
const { internal, sent } = makeProvider(client)
@@ -310,6 +312,25 @@ describe("KiloProvider sandbox toggle", () => {
enabled: true,
}),
)
expect(notice).toHaveBeenCalledTimes(1)
expect(notice).toHaveBeenCalledWith("Sandbox enabled")
notice.mockRestore()
})
it("reports the disabled state in a native notification", async () => {
const notice = spyOn(vscode.window, "showInformationMessage").mockResolvedValue(undefined)
const sandbox = defer<{ data: unknown }>()
const client = createClient({ sandboxDeferred: sandbox })
const { internal } = makeProvider(client)
internal.currentSession = mkSession()
const toggle = internal.handleToggleSandbox({ sessionID: "s1", requestID: "sandbox-1" })
sandbox.resolve({ data: { directory: "/repo", enabled: false, available: true, version: 2 } })
await toggle
expect(notice).toHaveBeenCalledTimes(1)
expect(notice).toHaveBeenCalledWith("Sandbox disabled")
notice.mockRestore()
})
it("shares session creation and finishes the toggle before a prompt", async () => {
@@ -341,6 +362,7 @@ describe("KiloProvider sandbox toggle", () => {
it("does not send a queued prompt when the sandbox toggle fails", async () => {
const log = spyOn(console, "error").mockImplementation(() => {})
const notice = spyOn(vscode.window, "showInformationMessage").mockResolvedValue(undefined)
const sandbox = defer<{ data: unknown }>()
const started = defer<void>()
const client = createClient({ sandboxDeferred: sandbox, sandboxStarted: started })
@@ -358,11 +380,14 @@ describe("KiloProvider sandbox toggle", () => {
expect(sent).toContainEqual(
expect.objectContaining({ type: "sendMessageFailed", sessionID: "s1", messageID: "message-1" }),
)
expect(notice).not.toHaveBeenCalled()
notice.mockRestore()
log.mockRestore()
})
it("does not send a queued prompt when the sandbox backend is unavailable", async () => {
const log = spyOn(console, "error").mockImplementation(() => {})
const notice = spyOn(vscode.window, "showInformationMessage").mockResolvedValue(undefined)
const sandbox = defer<{ data: unknown }>()
const started = defer<void>()
const client = createClient({ sandboxDeferred: sandbox, sandboxStarted: started })
@@ -382,6 +407,8 @@ describe("KiloProvider sandbox toggle", () => {
expect(sent).toContainEqual(
expect.objectContaining({ type: "sendMessageFailed", sessionID: "s1", messageID: "message-1" }),
)
expect(notice).not.toHaveBeenCalled()
notice.mockRestore()
log.mockRestore()
})
@@ -36,6 +36,17 @@ describe("PromptInput sandbox toggle", () => {
expect(toggle).not.toContain('type: "updateConfig"')
})
it("keeps success feedback out of the webview toast region", () => {
const start = src.indexOf("const handleSandboxMessage =")
const end = src.indexOf("const unsubscribe =", start)
const handler = src.slice(start, end)
expect(start).toBeGreaterThan(-1)
expect(end).toBeGreaterThan(start)
expect(handler).toContain('variant: "error"')
expect(handler).not.toContain('variant: "success"')
})
it("uses the internal flag for visibility and effective runtime state for the button", () => {
expect(src).toContain("features().sandboxControls")
expect(src).toContain("<Show when={sandboxVisible()}>")
@@ -464,19 +464,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
sandboxAttempts = 0
if (sandboxRetry) clearTimeout(sandboxRetry)
sandboxRetry = undefined
if (matching) {
if (!state.available) {
showToast({
variant: "error",
title: language.t("common.requestFailed"),
description: state.reason,
})
return true
}
if (matching && !state.available) {
showToast({
variant: "success",
title: language.t("settings.experimental.sandbox.title"),
description: language.t(state.enabled ? "prompt.action.sandbox.enabled" : "prompt.action.sandbox.disabled"),
variant: "error",
title: language.t("common.requestFailed"),
description: state.reason,
})
}
return true