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
5 changes: 5 additions & 0 deletions .changeset/agent-manager-model-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Fix agent-manager model sync on config change
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
const cached = vscode.getState<Record<string, unknown>>()
const [prompt, setPrompt] = createSignal((cached?.advancedDialogPrompt as string) ?? "")
const [versions, setVersions] = createSignal<VersionCount>(1)
const [model, setModel] = createSignal<{ providerID: string; modelID: string } | null>(session.selected())
const [model, setModel] = createSignal<{ providerID: string; modelID: string } | null>(session.configModel())
const [compareMode, setCompareMode] = createSignal(false)
const [modelAllocations, setModelAllocations] = createSignal<ModelAllocations>(new Map())
const [agent, setAgent] = createSignal(session.selectedAgent())
Expand Down Expand Up @@ -121,7 +121,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
// True when the user has changed the model from the session/config default
const overridden = createMemo(() => {
const sel = model()
const cfg = session.selected()
const cfg = session.configModel()
if (!sel || !cfg) return false
return sel.providerID !== cfg.providerID || sel.modelID !== cfg.modelID
})
Expand Down Expand Up @@ -441,7 +441,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
<Button
variant="ghost"
size="small"
onClick={() => setModel(session.selected())}
onClick={() => setModel(session.configModel())}
aria-label={t("prompt.action.resetModel")}
>
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
Expand Down
72 changes: 71 additions & 1 deletion packages/kilo-vscode/webview-ui/src/context/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@
* Also owns global (extension-lifetime) model selection (provider context is catalog-only).
*/

import { createContext, useContext, createSignal, createMemo, createEffect, onMount, onCleanup, batch } from "solid-js"
import {
createContext,
useContext,
createSignal,
createMemo,
createEffect,
onMount,
onCleanup,
batch,
untrack,
} from "solid-js"
import type { ParentComponent, Accessor } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store"
import { useVSCode } from "./vscode"
Expand Down Expand Up @@ -159,6 +169,7 @@ interface SessionContextValue {

// Model selection (global, extension-lifetime)
selected: (sessionID?: string) => ModelSelection | null
configModel: (sessionID?: string) => ModelSelection | null
selectModel: (providerID: string, modelID: string, sessionID?: string) => void
hasModelOverride: (sessionID?: string) => boolean
clearModelOverride: (sessionID?: string) => void
Expand Down Expand Up @@ -762,6 +773,64 @@ export const SessionProvider: ParentComponent = (props) => {
vscode.postMessage({ type: "requestFavorites" })
onCleanup(unsubFavorites)

// Clear model overrides that match the previous config model (not intentional user overrides).
// When config.model changes, old overrides that were just default values should be cleared
// so sessions fall through to resolveModel() and pick up the new config model.
const [lastConfigModel, setLastConfigModel] = createSignal<ModelSelection | null>(getGlobalModel())
createEffect(() => {
const newConfigModel = getGlobalModel()
// Use untrack to read previous value without making this effect re-trigger on its own updates
const oldConfigModel = untrack(() => lastConfigModel())
if (oldConfigModel) {
// Also clear when newConfigModel is null (user removed model from config)
if (newConfigModel) {
const modelChanged =
oldConfigModel.providerID !== newConfigModel.providerID || oldConfigModel.modelID !== newConfigModel.modelID
if (modelChanged) {
// Clear overrides that match the OLD config model - these were likely defaults,
// not intentional user overrides. Overrides that differ from both old and new
// config are preserved (intentional user selections).
setStore(
"sessionOverrides",
produce((overrides) => {
for (const sid of Object.keys(overrides)) {
const override = overrides[sid]
if (
override &&
override.providerID === oldConfigModel.providerID &&
override.modelID === oldConfigModel.modelID
) {
delete overrides[sid]
}
}
}),
)
}
} else {
// newConfigModel is null - clear all overrides that matched the old config model
// since the config no longer specifies a model. This ensures sessions fall through
// to provider defaults rather than using a stale removed model.
setStore(
"sessionOverrides",
produce((overrides) => {
for (const sid of Object.keys(overrides)) {
const override = overrides[sid]
if (
override &&
override.providerID === oldConfigModel.providerID &&
override.modelID === oldConfigModel.modelID
) {
delete overrides[sid]
}
}
}),
)
}
}
// Update the tracked config model
setLastConfigModel(newConfigModel)
})

function handleError(message: Extract<ExtensionMessage, { type: "error" }>) {
if (!message.sessionID || message.sessionID === currentSessionID()) setLoading(false)
if (message.sessionID) patchPage(message.sessionID, { loadingInitial: false, loadingOlder: false })
Expand Down Expand Up @@ -2371,6 +2440,7 @@ export const SessionProvider: ParentComponent = (props) => {
scopedQuestions,
scopedSuggestions,
selected,
configModel,
selectModel,
hasModelOverride,
clearModelOverride,
Expand Down
Loading