Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.
Merged
53 changes: 53 additions & 0 deletions packages/kilo-vscode/tests/unit/permission-queue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from "bun:test"
import { removeSessionPermissions, upsertPermission } from "../../webview-ui/src/context/permission-queue"
import type { PermissionRequest } from "../../webview-ui/src/types/messages"

const permission = (input: Partial<PermissionRequest> = {}): PermissionRequest => ({
id: input.id ?? "perm-1",
sessionID: input.sessionID ?? "session-1",
toolName: input.toolName ?? "read",
patterns: input.patterns ?? ["/tmp/*"],
args: input.args ?? {},
message: input.message,
tool: input.tool,
})

describe("permission queue", () => {
it("appends a new permission id", () => {
const result = upsertPermission([], permission({ id: "perm-1" }))
expect(result).toHaveLength(1)
expect(result[0].id).toBe("perm-1")
})

it("updates an existing permission id instead of duplicating", () => {
const existing = permission({ id: "perm-1", toolName: "read", patterns: ["a"] })
const incoming = permission({ id: "perm-1", toolName: "write", patterns: ["b"] })

const result = upsertPermission([existing], incoming)

expect(result).toHaveLength(1)
expect(result[0]).toEqual(incoming)
})

it("keeps other permission entries when updating one id", () => {
const first = permission({ id: "perm-1", sessionID: "session-1" })
const second = permission({ id: "perm-2", sessionID: "session-2" })
const incoming = permission({ id: "perm-1", toolName: "edit" })

const result = upsertPermission([first, second], incoming)

expect(result).toHaveLength(2)
expect(result.find((item) => item.id === "perm-1")).toEqual(incoming)
expect(result.find((item) => item.id === "perm-2")).toEqual(second)
})

it("removes only permissions from the deleted session", () => {
const first = permission({ id: "perm-1", sessionID: "session-1" })
const second = permission({ id: "perm-2", sessionID: "session-2" })
const third = permission({ id: "perm-3", sessionID: "session-1" })

const result = removeSessionPermissions([first, second, third], "session-1")

expect(result).toEqual([second])
})
})
16 changes: 11 additions & 5 deletions packages/kilo-vscode/webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,21 @@ export const DataBridge: Component<{ children: any }> = (props) => {

const data = createMemo(() => {
const id = session.currentSessionID()
const msgs = session.allMessages()
const parts = session.allParts()
const perms = id ? session.permissions().filter((p) => p.sessionID === id) : []
return {
session: session.sessions().map((s) => ({ ...s, id: s.id, role: "user" as const })) as unknown as any[],
session_status: {} as Record<string, any>,
session_diff: {} as Record<string, any[]>,
message: msgs as unknown as Record<string, SDKMessage[]>,
part: parts as unknown as Record<string, SDKPart[]>,
permission: id ? { [id]: session.permissions() as unknown as any[] } : {},
message: id ? { [id]: session.messages() as unknown as SDKMessage[] } : {},
part: id
? Object.fromEntries(
session
.messages()
.map((msg) => [msg.id, session.getParts(msg.id) as unknown as SDKPart[]])
.filter(([, parts]) => (parts as SDKPart[]).length > 0),
)
: {},
permission: id ? { [id]: perms as unknown as any[] } : {},
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
const sessionPermissions = () => session.permissions().filter((p) => p.sessionID === id())

const questionRequest = () => sessionQuestions()[0]
const permissionRequest = () => sessionPermissions()[0]
const permissionRequest = () => sessionPermissions().find((p) => !p.tool)
const blocked = () => sessionPermissions().length > 0 || sessionQuestions().length > 0

const [responding, setResponding] = createSignal(false)
Expand Down
13 changes: 13 additions & 0 deletions packages/kilo-vscode/webview-ui/src/context/permission-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { PermissionRequest } from "../types/messages"

export function upsertPermission(list: PermissionRequest[], permission: PermissionRequest) {
const idx = list.findIndex((item) => item.id === permission.id)
if (idx === -1) return [...list, permission]
const next = list.slice()
next[idx] = permission
return next
}

export function removeSessionPermissions(list: PermissionRequest[], sessionID: string) {
return list.filter((item) => item.sessionID !== sessionID)
}
4 changes: 3 additions & 1 deletion packages/kilo-vscode/webview-ui/src/context/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
ExtensionMessage,
FileAttachment,
} from "../types/messages"
import { removeSessionPermissions, upsertPermission } from "./permission-queue"

// Derive human-readable status from the last streaming part
function computeStatus(
Expand Down Expand Up @@ -508,7 +509,7 @@ export const SessionProvider: ParentComponent = (props) => {
}

function handlePermissionRequest(permission: PermissionRequest) {
setPermissions((prev) => [...prev, permission])
setPermissions((prev) => upsertPermission(prev, permission))
}

function handleQuestionRequest(question: QuestionRequest) {
Expand Down Expand Up @@ -603,6 +604,7 @@ export const SessionProvider: ParentComponent = (props) => {
return next
})
}
setPermissions((prev) => removeSessionPermissions(prev, sessionID))
setStatusMap(
produce((map) => {
delete map[sessionID]
Expand Down
Loading