Skip to content
Merged
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/agent-manager-sandbox-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Remember the Agent Manager new-worktree sandbox toggle for future sessions.
2 changes: 1 addition & 1 deletion packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
await this.fetchAndSendSandboxStatus(message.sessionID)
break
case "requestSandboxDefault":
await this.fetchAndSendSandboxDefault(message.contextDirectory)
await this.fetchAndSendSandboxDefault(message.contextDirectory, message.requestID)
break
case "setSandboxDefault":
await this.handleSetSandboxDefault(message.enabled, message.requestID, message.contextDirectory)
Expand Down
1 change: 1 addition & 0 deletions packages/kilo-vscode/src/agent-manager/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ interface SendCommandIn {

interface RequestSandboxDefaultIn {
type: "requestSandboxDefault"
requestID?: string
agentManagerContext?: string
contextDirectory?: string
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from "bun:test"
import { readFileSync } from "node:fs"
import { join } from "node:path"

const path = join(__dirname, "..", "..", "webview-ui", "agent-manager", "NewWorktreeDialog.tsx")
const providerPath = join(__dirname, "..", "..", "src", "KiloProvider.ts")
const src = readFileSync(path, "utf8")
const provider = readFileSync(providerPath, "utf8")

describe("NewWorktreeDialog sandbox toggle", () => {
it("uses the persisted default and only sends explicit modal overrides", () => {
Comment thread
marius-kilocode marked this conversation as resolved.
expect(src).toContain('vscode.postMessage({ type: "requestSandboxDefault", requestID: sandboxRequestID })')
expect(src).toContain('if (message.type !== "sandboxDefaultStatus") return')
expect(src).toContain("if (message.requestID !== sandboxRequestID) return")
expect(src).toContain("setSandbox(message.enabled)")
expect(src).toContain("setSandboxOverride(next === sandboxDefault() ? undefined : next)")
expect(src).toContain(
'vscode.postMessage({ type: "setSandboxDefault", enabled: next, requestID: sandboxRequestID })',
)
expect(src).toContain("sandbox: sandboxVisible() ? sandboxOverride() : undefined")
expect(src).toContain("const sandboxVisible = () => features().sandboxControls")
expect(provider).toContain("await this.fetchAndSendSandboxDefault(message.contextDirectory, message.requestID)")
expect(src).not.toContain("createSignal(config().experimental?.sandbox === true)")
expect(src).not.toContain("visible as isSandboxVisible")
})
})
11 changes: 8 additions & 3 deletions packages/kilo-vscode/tests/unit/sandbox-bootstrap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,15 @@ describe("Agent Manager sandbox startup", () => {
expect(provider).toContain("wt.result.path, wt.result.branch, session.id")
})

test("uses the experiment-aware visibility condition for UI and payload", () => {
expect(dialog).toContain("const sandboxVisible = () => isSandboxVisible(features(), config())")
expect(dialog).toContain("sandbox: sandboxVisible() ? sandbox() : undefined")
test("uses the persisted sandbox default for UI and only sends explicit overrides", () => {
expect(dialog).toContain("const sandboxVisible = () => features().sandboxControls")
expect(dialog).toContain('vscode.postMessage({ type: "requestSandboxDefault", requestID: sandboxRequestID })')
expect(dialog).toContain(
'vscode.postMessage({ type: "setSandboxDefault", enabled: next, requestID: sandboxRequestID })',
)
expect(dialog).toContain("sandbox: sandboxVisible() ? sandboxOverride() : undefined")
expect(dialog).toContain("<Show when={sandboxVisible()}>")
expect(dialog).not.toContain("visible as isSandboxVisible")
})

test("places the sandbox toggle with prompt actions instead of model selectors", () => {
Expand Down
68 changes: 56 additions & 12 deletions packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { ModelSelectorBase } from "../src/components/shared/ModelSelector"
import { ModeSwitcherBase } from "../src/components/shared/ModeSwitcher"
import { SpeechToTextButton } from "../src/components/speech-to-text/SpeechToTextButton"
import { canUseSpeechToText, selectedSpeechToTextModel } from "../src/components/speech-to-text/availability"
import { visible as isSandboxVisible } from "../src/components/settings/sandboxing"
import { ThinkingSelectorBase } from "../src/components/shared/ThinkingSelector"
import { SandboxButtonBase, SandboxTooltipContent } from "../src/components/shared/SandboxButton"
import {
Expand Down Expand Up @@ -104,8 +103,14 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
const [compareOpen, setCompareOpen] = createSignal(false)
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
const [variant, setVariant] = createSignal<string | undefined>(session.currentVariant())
const [sandbox, setSandbox] = createSignal(config().experimental?.sandbox === true)
const sandboxVisible = () => isSandboxVisible(features(), config())
const [sandbox, setSandbox] = createSignal<boolean | undefined>()
const [sandboxDefault, setSandboxDefault] = createSignal<boolean | undefined>()
const [sandboxOverride, setSandboxOverride] = createSignal<boolean | undefined>()
const [sandboxAvailable, setSandboxAvailable] = createSignal(true)
const [sandboxReason, setSandboxReason] = createSignal<string | undefined>()
const [sandboxRevision, setSandboxRevision] = createSignal(-1)
const sandboxRequestID = crypto.randomUUID()
const sandboxVisible = () => features().sandboxControls
const speech = useSpeechToText(vscode, server, { t })
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
const speechModel = () => selectedSpeechToTextModel(config())
Expand Down Expand Up @@ -146,6 +151,45 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
if (!stored || !list.includes(stored)) setVariant(list[0])
})

createEffect(() => {
if (!sandboxVisible()) return
if (server.connectionState() !== "connected") {
setSandbox(undefined)
setSandboxDefault(undefined)
setSandboxOverride(undefined)
return
}
vscode.postMessage({ type: "requestSandboxDefault", requestID: sandboxRequestID })
})

const unsubSandbox = vscode.onMessage((message) => {
if (message.type !== "sandboxDefaultStatus") return
if (message.requestID !== sandboxRequestID) return
Comment thread
marius-kilocode marked this conversation as resolved.
if (message.revision < sandboxRevision()) return

setSandboxRevision(message.revision)
setSandboxDefault(message.desired)
setSandboxAvailable(message.available)
setSandboxReason(message.reason)

const override = sandboxOverride()
if (override === undefined) {
setSandbox(message.enabled)
return
}
if (override === message.desired) setSandboxOverride(undefined)
})
onCleanup(unsubSandbox)

const toggleSandbox = () => {
const current = sandbox()
if (current === undefined || !sandboxAvailable()) return
const next = !current
setSandbox(next)
setSandboxOverride(next === sandboxDefault() ? undefined : next)
vscode.postMessage({ type: "setSandboxDefault", enabled: next, requestID: sandboxRequestID })
}

const imageAttach = useImageAttachments()
imageAttach.setFilePathDropHandler((paths) => {
const cwd = server.workspaceDirectory()
Expand Down Expand Up @@ -250,7 +294,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
baseBranch: advanced ? (baseBranch() ?? undefined) : undefined,
branchName: customBranch,
modelAllocations: allocations,
sandbox: sandboxVisible() ? sandbox() : undefined,
sandbox: sandboxVisible() ? sandboxOverride() : undefined,
files: imgFiles,
})

Expand Down Expand Up @@ -467,20 +511,20 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
<div class="prompt-input-hint-actions">
<Show when={sandboxVisible()}>
<SandboxButtonBase
enabled={sandbox()}
enabled={sandbox() ?? false}
available={sandbox() === undefined ? undefined : sandboxAvailable()}
reason={sandboxReason()}
disabled={sandbox() === undefined}
tooltip={
<SandboxTooltipContent
enabled={sandbox()}
enabled={sandbox() ?? false}
network={config().experimental?.sandbox_restrict_network !== false}
/>
}
tooltipClass="prompt-sandbox-tooltip-content"
onToggle={click(
"sandbox_toggle",
"configure_worktree_dialog",
() => setSandbox(!sandbox()),
() => ({ enabled: !sandbox() }),
)}
onToggle={click("sandbox_toggle", "configure_worktree_dialog", toggleSandbox, () => ({
enabled: !(sandbox() ?? false),
}))}
/>
</Show>
<Show when={canUseSpeech()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ export interface RequestSandboxStatusMessage {

export interface RequestSandboxDefaultMessage {
type: "requestSandboxDefault"
requestID?: string
agentManagerContext?: string
contextDirectory?: string
}
Expand Down
Loading