From 25c44b920673793e2e0d2711a70d72d65513a0b6 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Tue, 10 Mar 2026 18:39:34 +0100 Subject: [PATCH] fix(vscode): scope permissions/questions to current session family in ChatView The Agent Manager tracks all worktree sessions in a single KiloProvider, so permissions() and questions() contain entries from every session. PR #6850 removed the per-session filter, causing cross-session leakage where prompts from session A would appear in session B's dock. Add a sessionFamily memo that walks message parts to collect child session IDs (subagents) recursively, then filter permissions/questions to only the current session + its descendants. --- .../src/components/chat/ChatView.tsx | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 740d5683b65..1ae93ff14a7 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -3,7 +3,7 @@ * Main chat container that combines all chat components */ -import { Component, Show, createEffect, on, onCleanup, onMount } from "solid-js" +import { Component, Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" import { TaskHeader } from "./TaskHeader" @@ -32,21 +32,50 @@ export const ChatView: Component = (props) => { const id = () => session.currentSessionID() const hasMessages = () => session.messages().length > 0 const idle = () => session.status() !== "busy" - // Include ALL pending permissions/questions -- both from the current session - // and from child sessions (subagents). The extension host already filters - // SSE events to only tracked sessions, so everything in these lists is - // relevant to the current workspace. - const allPermissions = () => session.permissions() - const allQuestions = () => session.questions() - // Bottom-dock permission: prefer current-session permissions, - // then fall back to any pending permission (including child sessions). + // Collect child session IDs spawned by task tools in the current session + // (recursive: subagents can spawn their own subagents). + const sessionFamily = createMemo>(() => { + const root = id() + if (!root) return new Set() + const family = new Set([root]) + const parts = session.allParts() + const messages = session.allMessages() + const queue = [root] + while (queue.length > 0) { + const sid = queue.pop()! + const msgs = messages[sid] + if (!msgs) continue + for (const msg of msgs) { + const msgParts = parts[msg.id] + if (!msgParts) continue + for (const p of msgParts) { + if (p.type !== "tool") continue + const child = (p as { metadata?: { sessionId?: string } }).metadata?.sessionId + if (child && !family.has(child)) { + family.add(child) + queue.push(child) + } + } + } + } + return family + }) + + // Filter permissions/questions to the current session + its child sessions + // (subagents). In the Agent Manager all worktree sessions share one provider, + // so without this filter prompts from unrelated sessions would leak through. + const scopedPermissions = () => session.permissions().filter((p) => sessionFamily().has(p.sessionID)) + const scopedQuestions = () => session.questions().filter((q) => sessionFamily().has(q.sessionID)) + + // Bottom-dock permission: prefer current-session non-tool permissions, + // then fall back to any scoped pending permission (including child sessions). const questionRequest = () => - allQuestions().find((q) => q.sessionID === id() && !q.tool) ?? - allQuestions().find((q) => !q.tool) ?? - allQuestions()[0] - const permissionRequest = () => allPermissions().find((p) => p.sessionID === id()) ?? allPermissions()[0] - const blocked = () => allPermissions().length > 0 || allQuestions().length > 0 + scopedQuestions().find((q) => q.sessionID === id() && !q.tool) ?? + scopedQuestions().find((q) => !q.tool) ?? + scopedQuestions()[0] + const permissionRequest = () => scopedPermissions().find((p) => p.sessionID === id()) ?? scopedPermissions()[0] + const blocked = () => scopedPermissions().length > 0 || scopedQuestions().length > 0 // When a bottom-dock permission/question disappears while the session is busy, // the scroll container grows taller. Dispatch a custom event so MessageList can