From a641d3446b7e170c265f3224968feabd334ccb35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
Date: Thu, 12 Feb 2026 16:52:31 +0100
Subject: [PATCH 1/7] feat(vscode): add org switching HTTP client method and
message types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add setOrganization() to HttpClient for calling POST /kilo/organization.
Add SetOrganizationRequest (webview→extension) and NavigateMessage
(extension→webview) to the message type system.
---
.../src/services/cli-backend/http-client.ts | 9 +++++++++
.../kilo-vscode/webview-ui/src/types/messages.ts | 12 ++++++++++++
2 files changed, 21 insertions(+)
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 51992e90c39..1bc5dbb7a72 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,15 @@ 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 })
+ return true
+ }
+
// ============================================
// Auth Methods
// ============================================
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts
index 0168a209918..d4d0d3a9466 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: string
+}
+
export interface ProvidersLoadedMessage {
type: "providersLoaded"
providers: Record
@@ -482,6 +487,7 @@ export type ExtensionMessage =
| DeviceAuthCompleteMessage
| DeviceAuthFailedMessage
| DeviceAuthCancelledMessage
+ | NavigateMessage
| ProvidersLoadedMessage
| AgentsLoadedMessage
| QuestionRequestMessage
@@ -555,6 +561,11 @@ export interface CancelLoginRequest {
type: "cancelLogin"
}
+export interface SetOrganizationRequest {
+ type: "setOrganization"
+ organizationId: string | null
+}
+
export interface WebviewReadyRequest {
type: "webviewReady"
}
@@ -637,6 +648,7 @@ export type WebviewMessage =
| RefreshProfileRequest
| OpenExternalRequest
| CancelLoginRequest
+ | SetOrganizationRequest
| WebviewReadyRequest
| RequestProvidersMessage
| CompactRequest
From 99797f10a1bda248c8089a2f54ef8267f6bb498c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
Date: Thu, 12 Feb 2026 16:52:38 +0100
Subject: [PATCH 2/7] feat(vscode): handle org switching and post-login org
navigation
Handle setOrganization message to persist org selection and refresh
profile + providers. After login, auto-navigate to profile view when
the user belongs to organizations so they can pick one.
---
packages/kilo-vscode/src/KiloProvider.ts | 26 ++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index 71fb997b592..c728e830cd9 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -188,6 +188,9 @@ export class KiloProvider implements vscode.WebviewViewProvider {
case "logout":
await this.handleLogout()
break
+ case "setOrganization":
+ await this.handleSetOrganization(message.organizationId)
+ break
case "refreshProfile":
await this.handleRefreshProfile()
break
@@ -897,6 +900,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
@@ -908,6 +916,24 @@ 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 {
+ if (!this.httpClient) {
+ return
+ }
+
+ console.log("[Kilo New] KiloProvider: 🏢 Switching organization:", organizationId ?? "personal")
+ await this.httpClient.setOrganization(organizationId)
+
+ // Refresh profile (balance changes per org) and providers (models differ per org)
+ const profileData = await this.httpClient.getProfile()
+ this.postMessage({ type: "profileData", data: profileData })
+ await this.fetchAndSendProviders()
+ }
+
/**
* Handle logout request from the webview.
*/
From 74478272bc70f598ce019c4c3a0adfd6801ca9e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
Date: Thu, 12 Feb 2026 16:52:44 +0100
Subject: [PATCH 3/7] feat(vscode): add org selector dropdown to profile view
Show a dropdown in the profile view for users with organizations,
allowing them to switch between personal account and org accounts.
Handle navigate messages in App.tsx for post-login auto-navigation.
---
packages/kilo-vscode/webview-ui/src/App.tsx | 4 ++
.../webview-ui/src/components/ProfileView.tsx | 69 ++++++++++++++++++-
2 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx
index c90a9881d58..d6e675bd15f 100644
--- a/packages/kilo-vscode/webview-ui/src/App.tsx
+++ b/packages/kilo-vscode/webview-ui/src/App.tsx
@@ -119,6 +119,10 @@ const AppContent: Component = () => {
console.log("[Kilo New] App: 🎬 action:", message.action)
handleViewAction(message.action)
}
+ if (message?.type === "navigate" && message.view) {
+ console.log("[Kilo New] App: 🧭 navigate:", message.view)
+ setCurrentView(message.view)
+ }
}
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..f06c034c88e 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 } 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,47 @@ const formatBalance = (amount: number): string => {
return `$${amount.toFixed(2)}`
}
+interface OrgOption {
+ value: string | null
+ label: string
+ description?: string
+}
+
const ProfileView: Component = (props) => {
const vscode = useVSCode()
const language = useLanguage()
+ const [switching, setSwitching] = createSignal(false)
+
+ const orgOptions = createMemo(() => {
+ const orgs = props.profileData?.profile.organizations ?? []
+ if (orgs.length === 0) {
+ return []
+ }
+ return [
+ { value: null, label: "Personal Account" },
+ ...orgs.map((org) => ({ value: org.id, label: org.name, description: org.role })),
+ ]
+ })
+
+ const currentOrg = createMemo(() => {
+ const id = props.profileData?.currentOrgId ?? null
+ return orgOptions().find((o) => o.value === id)
+ })
+
+ const handleSelectOrg = (option: OrgOption) => {
+ if (option.value === (props.profileData?.currentOrgId ?? null)) {
+ return
+ }
+ setSwitching(true)
+ vscode.postMessage({ type: "setOrganization", organizationId: option.value })
+ // switching state resets when new profileData arrives
+ }
+
+ // Reset switching state when profile data changes (org switch completed)
+ createMemo(() => {
+ props.profileData
+ setSwitching(false)
+ })
const handleLogin = () => {
props.onLogin()
@@ -126,6 +165,34 @@ const ProfileView: Component = (props) => {
+ {/* Organization selector */}
+ 0}>
+
+
+ Account
+
+
+
+
{/* Balance */}
{(balance) => (
From 2b7dd9531414dc6749191a5ae60cfc170926f769 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
Date: Thu, 12 Feb 2026 17:46:14 +0100
Subject: [PATCH 4/7] fix(vscode): improve org selector robustness and type
safety
- Replace createMemo with createEffect for switching state reset side-effect
- Add error handling in handleSetOrganization to recover from failed switches
- Change setOrganization return type from boolean to void
- Narrow NavigateMessage.view to a union of valid view names
- Remove emoji from org switch log message
---
packages/kilo-vscode/src/KiloProvider.ts | 19 +++++++++++++------
.../src/services/cli-backend/http-client.ts | 3 +--
.../webview-ui/src/components/ProfileView.tsx | 16 ++++++++++------
.../webview-ui/src/types/messages.ts | 2 +-
4 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index c728e830cd9..b1325591a1e 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -925,13 +925,20 @@ export class KiloProvider implements vscode.WebviewViewProvider {
return
}
- console.log("[Kilo New] KiloProvider: 🏢 Switching organization:", organizationId ?? "personal")
- await this.httpClient.setOrganization(organizationId)
+ console.log("[Kilo New] KiloProvider: Switching organization:", organizationId ?? "personal")
+ try {
+ await this.httpClient.setOrganization(organizationId)
- // Refresh profile (balance changes per org) and providers (models differ per org)
- const profileData = await this.httpClient.getProfile()
- this.postMessage({ type: "profileData", data: profileData })
- await this.fetchAndSendProviders()
+ // Refresh profile (balance changes per org) and providers (models differ per org)
+ const profileData = await this.httpClient.getProfile()
+ this.postMessage({ type: "profileData", data: profileData })
+ await this.fetchAndSendProviders()
+ } 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 this.httpClient.getProfile()
+ this.postMessage({ type: "profileData", data: profileData })
+ }
}
/**
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 1bc5dbb7a72..e4240bde030 100644
--- a/packages/kilo-vscode/src/services/cli-backend/http-client.ts
+++ b/packages/kilo-vscode/src/services/cli-backend/http-client.ts
@@ -317,9 +317,8 @@ export class HttpClient {
* Switch the active organization.
* Pass null to switch back to personal account.
*/
- async setOrganization(organizationId: string | null): Promise {
+ async setOrganization(organizationId: string | null): Promise {
await this.request("POST", "/kilo/organization", { organizationId })
- return true
}
// ============================================
diff --git a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
index f06c034c88e..87eee7470dd 100644
--- a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
@@ -1,4 +1,4 @@
-import { Component, Show, createSignal, createMemo } from "solid-js"
+import { Component, Show, createSignal, createMemo, createEffect, on } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { Card } from "@kilocode/kilo-ui/card"
import { Select } from "@kilocode/kilo-ui/select"
@@ -56,11 +56,15 @@ const ProfileView: Component = (props) => {
// switching state resets when new profileData arrives
}
- // Reset switching state when profile data changes (org switch completed)
- createMemo(() => {
- props.profileData
- setSwitching(false)
- })
+ // Reset switching state when profile data changes (org switch completed or failed)
+ createEffect(
+ on(
+ () => props.profileData,
+ () => {
+ setSwitching(false)
+ },
+ ),
+ )
const handleLogin = () => {
props.onLogin()
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts
index d4d0d3a9466..705aef43b99 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts
@@ -402,7 +402,7 @@ export interface DeviceAuthCancelledMessage {
export interface NavigateMessage {
type: "navigate"
- view: string
+ view: "newTask" | "marketplace" | "history" | "profile" | "settings"
}
export interface ProvidersLoadedMessage {
From eec91fbac5819860928a53a4e890b575519906af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?=
Date: Thu, 12 Feb 2026 18:19:47 +0100
Subject: [PATCH 5/7] fix(vscode): fix org selector value handling and
switching state
- Use 'personal' string instead of null for Select option values to
avoid Kobalte value/identity mismatch
- Replace fragile createEffect with a target-tracking memo that only
shows switching state until the expected org arrives
- Rename handleSelectOrg to selectOrg per style guide
---
.../webview-ui/src/components/ProfileView.tsx | 53 +++++++++----------
1 file changed, 26 insertions(+), 27 deletions(-)
diff --git a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
index 87eee7470dd..0ef27decf23 100644
--- a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx
@@ -1,4 +1,4 @@
-import { Component, Show, createSignal, createMemo, createEffect, on } from "solid-js"
+import { Component, Show, createSignal, createMemo } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { Card } from "@kilocode/kilo-ui/card"
import { Select } from "@kilocode/kilo-ui/select"
@@ -20,8 +20,10 @@ const formatBalance = (amount: number): string => {
return `$${amount.toFixed(2)}`
}
+const PERSONAL = "personal"
+
interface OrgOption {
- value: string | null
+ value: string
label: string
description?: string
}
@@ -29,43 +31,40 @@ interface OrgOption {
const ProfileView: Component = (props) => {
const vscode = useVSCode()
const language = useLanguage()
- const [switching, setSwitching] = createSignal(false)
+ const [target, setTarget] = createSignal(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 []
- }
+ if (orgs.length === 0) return []
return [
- { value: null, label: "Personal Account" },
+ { 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 ?? null
+ const id = props.profileData?.currentOrgId ?? PERSONAL
return orgOptions().find((o) => o.value === id)
})
- const handleSelectOrg = (option: OrgOption) => {
- if (option.value === (props.profileData?.currentOrgId ?? null)) {
- return
- }
- setSwitching(true)
- vscode.postMessage({ type: "setOrganization", organizationId: option.value })
- // switching state resets when new profileData arrives
+ 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,
+ })
}
- // Reset switching state when profile data changes (org switch completed or failed)
- createEffect(
- on(
- () => props.profileData,
- () => {
- setSwitching(false)
- },
- ),
- )
-
const handleLogin = () => {
props.onLogin()
}
@@ -186,9 +185,9 @@ const ProfileView: Component = (props) => {