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
123 changes: 63 additions & 60 deletions packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,70 +892,73 @@ describe("Agent Manager — VS Code import boundary", () => {
})
})

// ---------------------------------------------------------------------------
// Provider chain parity — sidebar App.tsx vs AgentManagerApp.tsx
//
// The agent manager reuses ChatView (and therefore MessageList, etc.) from the
// sidebar. Any context provider that ChatView's tree may call useXxx() on must
// also be present in the agent manager's provider chain. A missing provider
// crashes the entire SolidJS component tree silently.
//
// Regression: PR #7473 moved KiloNotifications into MessageList. It calls
// useNotifications(), but NotificationsProvider was only in App.tsx — the agent
// manager rendered a blank screen.
// ---------------------------------------------------------------------------

const APP_FILE = path.join(ROOT, "webview-ui/src/App.tsx")
const AGENT_MANAGER_APP_FILE = path.join(ROOT, "webview-ui/agent-manager/AgentManagerApp.tsx")
const PROVIDER_SHELL_FILE = path.join(ROOT, "webview-ui/src/context/provider-shell.tsx")

describe("Agent Manager — provider chain parity with sidebar", () => {
/**
* Extract provider component names used as JSX elements in a file.
* Matches `<FooProvider` and `<FooProvider>` patterns, returning the names.
*/
function extractProviders(content: string): string[] {
const matches = [...content.matchAll(/<(\w+Provider)\b/g)]
return [...new Set(matches.map((m) => m[1]!))]
describe("Shared webview provider shell", () => {
function ordered(source: string, names: string[]) {
const positions = names.map((name) => source.indexOf(`<${name}`))
expect(
positions.every((position) => position >= 0),
`Missing provider from ${names.join(" -> ")}`,
).toBe(true)
expect(positions).toEqual([...positions].sort((a, b) => a - b))
}

/**
* Providers that the agent manager intentionally omits because it does not
* use the components that depend on them. If a shared component (ChatView,
* MessageList, etc.) starts using one of these, the test will fail and
* force the developer to add the provider to AgentManagerApp.tsx.
*/
const KNOWN_EXCLUSIONS: string[] = [
// These are wrapped by LanguageBridge and DataBridge respectively,
// which the agent manager already includes in its provider chain.
"LanguageProvider",
"DataProvider",
// Agent Manager owns its local session tabs and ChatView only reads this
// optional context in the standard sidebar/editor webview.
"LocalTabsProvider",
// Work-style onboarding is injected only into the sidebar empty state.
"WorkStyleProvider",
]

it("agent manager includes all context providers from sidebar App.tsx", () => {
const sidebar = fs.readFileSync(APP_FILE, "utf-8")
const agent = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8")

const sidebarProviders = extractProviders(sidebar)
const agentProviders = extractProviders(agent)
const agentSet = new Set(agentProviders)
const excluded = new Set(KNOWN_EXCLUSIONS)

const missing = sidebarProviders.filter((p) => !agentSet.has(p) && !excluded.has(p))

expect(
missing,
`These providers are in App.tsx but missing from AgentManagerApp.tsx.\n` +
`The agent manager reuses ChatView — any provider that ChatView's component\n` +
`tree depends on must be present in both provider chains.\n\n` +
`Missing providers:\n` +
missing.map((p) => ` - ${p}`).join("\n") +
`\n\nFix: add the missing <${missing[0]}> to AgentManagerApp.tsx's provider chain,\n` +
`or add it to KNOWN_EXCLUSIONS with a justification if it's truly unused.`,
).toEqual([])
it("owns the common provider order and bridges", () => {
const source = fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")
ordered(source, [
"ThemeProvider",
"DialogProvider",
"VSCodeProvider",
"MermaidDownloadBridge",
"ServerProvider",
"LanguageBridge",
"MarkedProvider",
"DiffComponentProvider",
"CodeComponentProvider",
"FileComponentProvider",
"ProviderProvider",
"ConfigProvider",
"SpeechToTextPrewarm",
"DisplayProvider",
"IndexingProvider",
"KiloEmbeddingModelsProvider",
"ImageModelsProvider",
"NotificationsProvider",
"SessionProvider",
"AgentRequirementsProvider",
"MemoryProvider",
"FeedbackProvider",
])
expect(source.indexOf("<Toast.Region")).toBeGreaterThan(source.indexOf("</VSCodeProvider>"))
})

it("keeps sidebar-only providers in the sidebar root", () => {
const source = fs.readFileSync(APP_FILE, "utf-8")
ordered(source, [
"ProviderShell.Root",
"WorkStyleProvider",
"ProviderShell.Session",
"LocalTabsProvider",
"ProviderShell.Chat",
"DataBridge",
"AppContent",
])
expect(fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")).not.toMatch(/WorkStyleProvider|LocalTabsProvider/)
})

it("keeps worktree mode in the Agent Manager root", () => {
const source = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8")
ordered(source, [
"ProviderShell.Root",
"ProviderShell.Session",
"ProviderShell.Chat",
"WorktreeModeProvider",
"DataBridge",
"AgentManagerContent",
])
expect(fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")).not.toContain("WorktreeModeProvider")
})
})
91 changes: 17 additions & 74 deletions packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import type {
SessionCreatedMessage,
BranchInfo,
} from "../src/types/messages"
import { IndexingProvider } from "../src/context/indexing"
import {
DragDropProvider,
DragDropSensors,
Expand All @@ -49,43 +48,24 @@ import {
createSortable,
} from "@thisbeyond/solid-dnd"
import type { DragEvent } from "@thisbeyond/solid-dnd"
import { ThemeProvider } from "@kilocode/kilo-ui/theme"
import { DialogProvider, useDialog } from "@kilocode/kilo-ui/context/dialog"
import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { Dialog } from "@kilocode/kilo-ui/dialog"
import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu"
import { MarkedProvider } from "@kilocode/kilo-ui/context/marked"
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
import { FileComponentProvider } from "@kilocode/kilo-ui/context/file"
import { Code } from "@kilocode/kilo-ui/code"
import { Diff } from "@kilocode/kilo-ui/diff"
import { File } from "@kilocode/kilo-ui/file"
import { Toast, showToast } from "@kilocode/kilo-ui/toast"
import { showToast } from "@kilocode/kilo-ui/toast"
import { ResizeHandle } from "@kilocode/kilo-ui/resize-handle"
import { Icon } from "@kilocode/kilo-ui/icon"
import { Button } from "@kilocode/kilo-ui/button"
import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { Spinner } from "@kilocode/kilo-ui/spinner"
import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
import { VSCodeProvider, useVSCode } from "../src/context/vscode"
import { ServerProvider } from "../src/context/server"
import { ProviderProvider } from "../src/context/provider"
import { ConfigProvider } from "../src/context/config"
import { DisplayProvider } from "../src/context/display"
import { KiloEmbeddingModelsProvider } from "../src/context/kilo-embedding-models"
import { ImageModelsProvider } from "../src/context/image-models"
import { NotificationsProvider } from "../src/context/notifications"
import { FeedbackProvider } from "../src/context/feedback"
import { MemoryProvider } from "../src/context/memory"
import { SessionProvider, useSession } from "../src/context/session"
import { AgentRequirementsProvider } from "../src/context/agent-requirements"
import { useVSCode } from "../src/context/vscode"
import { useSession } from "../src/context/session"
import { WorktreeModeProvider } from "../src/context/worktree-mode"
import { ProviderShell } from "../src/context/provider-shell"
import { ChatView } from "../src/components/chat"
import { SpeechToTextPrewarm } from "../src/components/speech-to-text/SpeechToTextPrewarm"
import HistoryView from "../src/components/history/HistoryView"
import { NewWorktreeDialog } from "./NewWorktreeDialog"
import { DataBridge, MermaidDownloadBridge } from "../src/App"
import { LanguageBridge } from "../src/context/language-bridge"
import { DataBridge } from "../src/App"
import { useLanguage } from "../src/context/language"
import { createTabFocus } from "../src/utils/tab-navigation"
import {
Expand Down Expand Up @@ -2880,53 +2860,16 @@ const AgentManagerContent: Component = () => {

export const AgentManagerApp: Component = () => {
return (
<ThemeProvider defaultTheme="kilo-vscode">
<DialogProvider>
<VSCodeProvider>
<MermaidDownloadBridge />
<ServerProvider>
<LanguageBridge>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<FileComponentProvider component={File}>
<ProviderProvider>
<ConfigProvider>
<SpeechToTextPrewarm />
<DisplayProvider>
<IndexingProvider>
<KiloEmbeddingModelsProvider>
<ImageModelsProvider>
<NotificationsProvider>
<SessionProvider>
<AgentRequirementsProvider>
<MemoryProvider>
<FeedbackProvider>
<WorktreeModeProvider>
<DataBridge>
<AgentManagerContent />
</DataBridge>
</WorktreeModeProvider>
</FeedbackProvider>
</MemoryProvider>
</AgentRequirementsProvider>
</SessionProvider>
</NotificationsProvider>
</ImageModelsProvider>
</KiloEmbeddingModelsProvider>
</IndexingProvider>
</DisplayProvider>
</ConfigProvider>
</ProviderProvider>
</FileComponentProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</MarkedProvider>
</LanguageBridge>
</ServerProvider>
</VSCodeProvider>
<Toast.Region />
</DialogProvider>
</ThemeProvider>
<ProviderShell.Root>
<ProviderShell.Session>
<ProviderShell.Chat>
<WorktreeModeProvider>
<DataBridge>
<AgentManagerContent />
</DataBridge>
</WorktreeModeProvider>
</ProviderShell.Chat>
</ProviderShell.Session>
</ProviderShell.Root>
)
}
Loading
Loading