diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index 75ab9751ce3..a728e472eff 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -190,6 +190,11 @@ export class KiloProvider implements vscode.WebviewViewProvider {
case "logout":
await this.handleLogout()
break
+ case "setOrganization":
+ if (typeof message.organizationId === "string" || message.organizationId === null) {
+ await this.handleSetOrganization(message.organizationId)
+ }
+ break
case "refreshProfile":
await this.handleRefreshProfile()
break
@@ -926,6 +931,11 @@ export class KiloProvider implements vscode.WebviewViewProvider {
const profileData = await this.httpClient.getProfile()
this.postMessage({ type: "profileData", data: profileData })
this.postMessage({ type: "deviceAuthComplete" })
+
+ // Step 5: If user has organizations, navigate to profile view so they can pick one
+ if (profileData?.profile.organizations && profileData.profile.organizations.length > 0) {
+ this.postMessage({ type: "navigate", view: "profile" })
+ }
} catch (error) {
if (attempt !== this.loginAttempt) {
return
@@ -937,6 +947,41 @@ export class KiloProvider implements vscode.WebviewViewProvider {
}
}
+ /**
+ * Handle organization switch request from the webview.
+ * Persists the selection and refreshes profile + providers since both change with org context.
+ */
+ private async handleSetOrganization(organizationId: string | null): Promise {
+ const client = this.httpClient
+ if (!client) {
+ return
+ }
+
+ console.log("[Kilo New] KiloProvider: Switching organization:", organizationId ?? "personal")
+ try {
+ await client.setOrganization(organizationId)
+ } catch (error) {
+ console.error("[Kilo New] KiloProvider: Failed to switch organization:", error)
+ // Re-fetch current profile to reset webview state (clears switching indicator)
+ const profileData = await client.getProfile()
+ this.postMessage({ type: "profileData", data: profileData })
+ return
+ }
+
+ // Org switch succeeded — refresh profile and providers independently (best-effort)
+ try {
+ const profileData = await client.getProfile()
+ this.postMessage({ type: "profileData", data: profileData })
+ } catch (error) {
+ console.error("[Kilo New] KiloProvider: Failed to refresh profile after org switch:", error)
+ }
+ try {
+ await this.fetchAndSendProviders()
+ } catch (error) {
+ console.error("[Kilo New] KiloProvider: Failed to refresh providers after org switch:", error)
+ }
+ }
+
/**
* Handle logout request from the webview.
*/
diff --git a/packages/kilo-vscode/src/services/cli-backend/http-client.ts b/packages/kilo-vscode/src/services/cli-backend/http-client.ts
index 2deba34f723..d35e48a84be 100644
--- a/packages/kilo-vscode/src/services/cli-backend/http-client.ts
+++ b/packages/kilo-vscode/src/services/cli-backend/http-client.ts
@@ -313,6 +313,14 @@ export class HttpClient {
}
}
+ /**
+ * Switch the active organization.
+ * Pass null to switch back to personal account.
+ */
+ async setOrganization(organizationId: string | null): Promise {
+ await this.request("POST", "/kilo/organization", { organizationId })
+ }
+
// ============================================
// FIM Completion Methods
// ============================================
diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx
index c90a9881d58..c16857461ef 100644
--- a/packages/kilo-vscode/webview-ui/src/App.tsx
+++ b/packages/kilo-vscode/webview-ui/src/App.tsx
@@ -18,6 +18,7 @@ import type { Message as SDKMessage, Part as SDKPart } from "@kilocode/sdk/v2"
import "./styles/chat.css"
type ViewType = "newTask" | "marketplace" | "history" | "profile" | "settings"
+const VALID_VIEWS = new Set(["newTask", "marketplace", "history", "profile", "settings"])
const DummyView: Component<{ title: string }> = (props) => {
return (
@@ -119,6 +120,10 @@ const AppContent: Component = () => {
console.log("[Kilo New] App: 🎬 action:", message.action)
handleViewAction(message.action)
}
+ if (message?.type === "navigate" && message.view && VALID_VIEWS.has(message.view)) {
+ console.log("[Kilo New] App: 🧭 navigate:", message.view)
+ setCurrentView(message.view as ViewType)
+ }
}
window.addEventListener("message", handler)
onCleanup(() => window.removeEventListener("message", handler))
diff --git a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
index f52643532c3..5bea5f827d2 100644
--- a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
@@ -1,6 +1,7 @@
-import { Component, Show } from "solid-js"
+import { Component, Show, createSignal, createMemo, createEffect } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { Card } from "@kilocode/kilo-ui/card"
+import { Select } from "@kilocode/kilo-ui/select"
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
import { useVSCode } from "../context/vscode"
import { useLanguage } from "../context/language"
@@ -19,9 +20,56 @@ const formatBalance = (amount: number): string => {
return `$${amount.toFixed(2)}`
}
+const PERSONAL = "personal"
+
+interface OrgOption {
+ value: string
+ label: string
+ description?: string
+}
+
const ProfileView: Component = (props) => {
const vscode = useVSCode()
const language = useLanguage()
+ const [target, setTarget] = createSignal(null)
+
+ // Reset pending target whenever profileData changes (success or failure both send a fresh profile)
+ createEffect(() => {
+ props.profileData // track
+ setTarget(null)
+ })
+
+ const switching = createMemo(() => {
+ const t = target()
+ if (t === null) return false
+ const current = props.profileData?.currentOrgId ?? PERSONAL
+ return current !== t
+ })
+
+ const orgOptions = createMemo(() => {
+ const orgs = props.profileData?.profile.organizations ?? []
+ if (orgs.length === 0) return []
+ return [
+ { value: PERSONAL, label: "Personal Account" },
+ ...orgs.map((org) => ({ value: org.id, label: org.name, description: org.role })),
+ ]
+ })
+
+ const currentOrg = createMemo(() => {
+ const id = props.profileData?.currentOrgId ?? PERSONAL
+ return orgOptions().find((o) => o.value === id)
+ })
+
+ const selectOrg = (option: OrgOption | undefined) => {
+ if (!option) return
+ const current = props.profileData?.currentOrgId ?? PERSONAL
+ if (option.value === current) return
+ setTarget(option.value)
+ vscode.postMessage({
+ type: "setOrganization",
+ organizationId: option.value === PERSONAL ? null : option.value,
+ })
+ }
const handleLogin = () => {
props.onLogin()
@@ -126,6 +174,34 @@ const ProfileView: Component = (props) => {
+ {/* Organization selector */}
+ 0}>
+
+
+ Account
+
+
+
+
{/* Balance */}
{(balance) => (
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts
index cc347352dbe..a5f714db3dd 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts
@@ -400,6 +400,11 @@ export interface DeviceAuthCancelledMessage {
type: "deviceAuthCancelled"
}
+export interface NavigateMessage {
+ type: "navigate"
+ view: "newTask" | "marketplace" | "history" | "profile" | "settings"
+}
+
export interface ProvidersLoadedMessage {
type: "providersLoaded"
providers: Record
@@ -497,6 +502,7 @@ export type ExtensionMessage =
| DeviceAuthCompleteMessage
| DeviceAuthFailedMessage
| DeviceAuthCancelledMessage
+ | NavigateMessage
| ProvidersLoadedMessage
| AgentsLoadedMessage
| AutocompleteSettingsLoadedMessage
@@ -572,6 +578,11 @@ export interface CancelLoginRequest {
type: "cancelLogin"
}
+export interface SetOrganizationRequest {
+ type: "setOrganization"
+ organizationId: string | null
+}
+
export interface WebviewReadyRequest {
type: "webviewReady"
}
@@ -674,6 +685,7 @@ export type WebviewMessage =
| RefreshProfileRequest
| OpenExternalRequest
| CancelLoginRequest
+ | SetOrganizationRequest
| WebviewReadyRequest
| RequestProvidersMessage
| CompactRequest