Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
da064ee
feat(jetbrains): add agent behavior settings foundation
kirillk Jun 17, 2026
547b40f
feat(jetbrains): polish settings lists and badges
kirillk Jun 18, 2026
e0ff8d1
fix(jetbrains): load agent behavior settings
kirillk Jun 18, 2026
9496a1c
feat(jetbrains): edit agent settings
kirillk Jun 19, 2026
9ea2170
feat(jetbrains): restrict agent editor behavior
kirillk Jun 19, 2026
30d3587
fix(jetbrains): polish agent settings interactions
kirillk Jun 20, 2026
4295d11
feat(jetbrains): add custom agent creation
kirillk Jun 21, 2026
5e28a80
fix(jetbrains): stabilize settings apply lifecycle
kirillk Jun 22, 2026
ebbc06b
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jun 22, 2026
68623a0
fix(jetbrains): resolve main merge compile errors
kirillk Jun 22, 2026
9061659
fix(cli): resolve workspace type imports
kirillk Jun 22, 2026
18cafe3
test(jetbrains): add agent settings coverage
kirillk Jun 22, 2026
8fdfce1
fix(jetbrains): refine agent settings visibility UI
kirillk Jun 22, 2026
b35da53
fix(jetbrains): remove redundant settings list headers
kirillk Jun 22, 2026
b5fc917
fix(jetbrains): refresh settings lists after mutations
kirillk Jun 23, 2026
35bc12e
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jun 25, 2026
e96bed0
docs: add JetBrains session error logging plan
kirillk Jun 25, 2026
3380f73
fix(jetbrains): equal settings list row heights and unified Delete label
kirillk Jun 27, 2026
ac104dd
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jun 29, 2026
16821c2
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jun 29, 2026
1d3348a
feat(jetbrains): export custom agents
kirillk Jun 29, 2026
bbba32b
fix(jetbrains): hide non-removable agent delete actions
kirillk Jun 29, 2026
fea54c3
fix(jetbrains): show friendly agent delete failure
kirillk Jun 29, 2026
a07c7a2
docs(jetbrains): document settings UI primitives
kirillk Jun 30, 2026
dda98c5
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jun 30, 2026
b852762
fix(jetbrains): load MCP settings with resolved project dir
kirillk Jun 30, 2026
30d432b
fix(jetbrains): configure settings list row layout
kirillk Jun 30, 2026
17aebf9
fix(jetbrains): edit project MCP settings
kirillk Jun 30, 2026
f276617
fix(jetbrains): remove rules skills workflows settings
kirillk Jul 1, 2026
3847122
fix(jetbrains): stage agent settings changes
kirillk Jul 1, 2026
14d14fd
fix(jetbrains): persist mcp env edits
kirillk Jul 1, 2026
2f29ba2
fix(jetbrains): update mcp add hint
kirillk Jul 1, 2026
beca569
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jul 1, 2026
fd3042c
fix(jetbrains): remediate settings apply lifecycle
kirillk Jul 1, 2026
8cb93ea
Merge branch 'main' into beneficial-auroraceratops
kirillk Jul 1, 2026
e2ba256
fix(jetbrains): address agent behavior review
kirillk Jul 2, 2026
03629b5
fix(cli): use config overlay targets for agent removal
kirillk Jul 2, 2026
193e52c
fix: repair agent behavior CI failures
kirillk Jul 2, 2026
887f6f9
Merge branch 'main' into beneficial-auroraceratops
kirillk Jul 2, 2026
8ec9c80
test(jetbrains): share list mouse event helper
kirillk Jul 2, 2026
094fed0
chore: update local agent and run configs
kirillk Jul 2, 2026
2bc1b04
Merge branch 'main' into beneficial-auroraceratops
kirillk Jul 2, 2026
8cdbf8a
Revert "chore: update local agent and run configs"
kirillk Jul 2, 2026
c8e66ae
fix(jetbrains): adapt model details badges
kirillk Jul 2, 2026
e1379b0
fix(vscode): rely on CLI agent removal
kirillk Jul 2, 2026
0e57f82
fix(vscode): tidy agent removal refresh
kirillk Jul 2, 2026
a5f484c
chore: remove local plan from branch
kirillk Jul 2, 2026
b828581
Merge remote-tracking branch 'origin/main' into beneficial-auroracera…
kirillk Jul 2, 2026
9ba4c1f
chore: add agent removal follow-up plan
kirillk Jul 2, 2026
d5f1965
chore: remove local plan file
kirillk Jul 2, 2026
e595890
fix(jetbrains): make workspace load cleanup deterministic
kirillk Jul 2, 2026
3d4a5ac
fix(vscode): stabilize visual regression snapshots
kirillk Jul 2, 2026
de94694
chore: update kilo-vscode visual regression baselines
github-actions[bot] Jul 2, 2026
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
6 changes: 6 additions & 0 deletions .changeset/staged-agent-settings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"@kilocode/kilo-jetbrains": patch
---

Improve JetBrains agent, MCP, provider, and model settings so changes are staged until Apply, persist through the CLI, reload accurately, and hide unsupported removal actions.
33 changes: 33 additions & 0 deletions packages/kilo-jetbrains/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ Official references:
- [User Interface Components](https://plugins.jetbrains.com/docs/intellij/user-interface-components.html)
- [UI FAQ (colors, borders, icons)](https://plugins.jetbrains.com/docs/intellij/ui-faq.html)

- For compact icon-only actions, use `ai.kilocode.client.ui.HoverIcon` so the control gets the standard 24×24 hover treatment. Do not create `JButton(icon)` or wrap a bare icon in a button just to make it clickable.
- **Reuse platform icons**: browse at https://intellij-icons.jetbrains.design. Access via `AllIcons.*` constants.
- Custom icons: SVG files in `resources/icons/`. Load via `IconLoader.getIcon("/icons/foo.svg", MyClass::class.java)`.
- Organize in an `icons` package or a `*Icons` object with `@JvmField` on each constant.
Expand Down Expand Up @@ -583,6 +584,38 @@ Review generated UI code and remove:
- Extra helpers that do not make the UI clearer or more reusable
- Any Kotlin UI DSL (`com.intellij.ui.dsl.builder`) introduced by accident

## Settings UI

Settings UI has reusable primitives in `frontend/src/main/kotlin/ai/kilocode/client/settings/base/`. Check these before adding new settings components or custom Swing assemblies.

### Base Pages And Messaging

- Use `BaseSettingsUi` for app-backed draft settings that need app-state collection, workspace loading/refreshing, draft/baseline tracking, save progress, save failure handling, and login/banner integration.
- Use `SettingsPanel` and `SettingsOverlayPanel` as the settings surface so progress and errors go through `showProgress`, `updateProgress`, `showError`, and `clearProgress`.
- Use `SettingsTop` for settings banners and login prompts rather than ad hoc labels, notifications, or dialog prompts embedded in the form.
- Use `SettingsDraftState` and `SettingsDraftPage` for modified/reset/apply behavior instead of maintaining unrelated local dirty-state mechanisms.
- Use the base loading and refresh flow (`BaseSettingsUi` or `SettingsListPanel.reload` / `mutateAndReload`) so busy state, refresh selection, and app readiness are handled consistently.
- Communicate load, refresh, validation, and save errors through the common settings messaging mechanisms: overlay `showError`, `SettingsMessageException` for user-facing list mutation errors, `failedText()` / `saveError` in `BaseSettingsUi`, and `SettingsTop` banners for persistent page-level problems.

### Rows And Forms

- Use `SettingsRow`, `SettingsStackedRow`, and `SettingsRows` for reusable setting rows, stacked text/editing rows, keyed dynamic rows, and setting sections.
- Do not create a custom row panel for each setting unless the common row classes cannot represent the behavior.
- Keep settings UI on the EDT and continue using existing platform Swing components, `Stack`, `Align`, `UiStyle`, and localized `KiloBundle` strings according to the UI guidance above.

### Lists And Add/Remove Collections

- For add/remove/edit collections, use the shared list infrastructure: `SettingsListPanel`, `SettingsListView`, `SettingsListItem`, `SettingsListCell`, `SettingsListSelection`, and `SettingsToolbarAction` where applicable.
- When a setting is a list of values that can be added or removed inline, represent it with common list/editor primitives, toolbar actions, and in-place cells/buttons as needed.
- Do not build a bespoke set of Swing components for each add/remove list situation.
- Prefer list action cells (`SettingsListCell`) for row-local actions like edit/delete and toolbar actions for global add/import/refresh actions.

### Settings Test Coverage Pattern

- Each settings page that writes state needs a fake-RPC frontend test that proves UI interactions call the expected client service/RPC method.
- Each backend-backed settings write path needs a `*RpcApiImpl` or manager test against `MockCliServer` that asserts the exact CLI HTTP body and that a subsequent reload observes the persisted value.
- Navigation-only settings pages should still have `BasePlatformTestCase` coverage for rendered child links, stable child IDs, and inert `isModified`/`apply` behavior.

## Session Component

The chat session feature uses a three-layer Model / Controller / View architecture. All files live under
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ai.kilocode.backend.app

import ai.kilocode.jetbrains.api.model.Config
import ai.kilocode.jetbrains.api.model.KiloNotifications200ResponseInner
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
import ai.kilocode.backend.migration.LegacyMigrationDetection
import ai.kilocode.rpc.dto.ConfigDto

/**
* Full application lifecycle state, combining CLI transport connection
Expand Down Expand Up @@ -54,7 +54,7 @@ data class ConfigWarning(
*/
data class AppData(
val profile: KiloProfile200Response?,
val config: Config,
val config: ConfigDto,
val notifications: List<KiloNotifications200ResponseInner>,
val warnings: List<ConfigWarning> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import ai.kilocode.jetbrains.api.infrastructure.ClientError
import ai.kilocode.jetbrains.api.infrastructure.ClientException
import ai.kilocode.jetbrains.api.infrastructure.ServerError
import ai.kilocode.jetbrains.api.infrastructure.ServerException
import ai.kilocode.jetbrains.api.model.Config
import ai.kilocode.jetbrains.api.model.ConfigWarnings200ResponseInner
import ai.kilocode.jetbrains.api.model.KiloNotifications200ResponseInner
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
import ai.kilocode.jetbrains.api.model.ProviderOauthAuthorizeRequest
import ai.kilocode.jetbrains.api.model.ProviderOauthCallbackRequest
import ai.kilocode.rpc.dto.ConfigDto
import ai.kilocode.rpc.dto.DeviceAuthDto
import ai.kilocode.rpc.dto.ConfigPatchDto
import ai.kilocode.rpc.dto.HealthDto
Expand All @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
Expand All @@ -56,6 +57,8 @@ import java.net.ConnectException
import java.net.SocketTimeoutException
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
* App-level orchestrator that owns the CLI server lifecycle and
Expand Down Expand Up @@ -130,7 +133,7 @@ class KiloBackendAppService private constructor(
@Volatile var profile: KiloProfile200Response? = null
private set

@Volatile var config: Config? = null
@Volatile var config: ConfigDto? = null
private set

@Volatile var notifications: List<KiloNotifications200ResponseInner> = emptyList()
Expand Down Expand Up @@ -245,6 +248,7 @@ class KiloBackendAppService private constructor(
suspend fun updateConfig(patch: ConfigPatchDto): KiloAppState {
val http = connection.apiClient ?: throw IllegalStateException("Not connected")
val current = _appState.value as? KiloAppState.Ready ?: throw IllegalStateException("Kilo backend is not ready")
val connected = connection.state.value as? ConnectionState.Connected
val body = KiloCliDataParser.buildConfigPatch(patch)
val summary = summary(patch)
log.info("Global config patch: started $summary")
Expand All @@ -256,14 +260,23 @@ class KiloBackendAppService private constructor(
withContext(Dispatchers.IO) {
http.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
val text = response.body?.string()
log.warn("Global config patch failed: HTTP ${response.code} ${response.message} $summary ${text.orEmpty()}")
log.warn("Global config patch failed: HTTP ${response.code} ${response.message} $summary")
throw IllegalStateException("Global config patch failed: HTTP ${response.code} ${response.message}")
}
}
}
log.info("Global config patch: saved $summary")
refreshConfigState()
val cfg = fetchConfig().value
if (cfg == null) {
log.warn("Global config patch: config reload failed after save $summary")
return (_appState.value as? KiloAppState.Ready) ?: current
}
val warns = fetchWarnings()
val state = _appState.value
if (state is KiloAppState.Ready && state.data === current.data && connection.state.value == connected) {
config = cfg
setAppReady(current.data.copy(config = cfg, warnings = warns))
}
log.info("Global config patch: state refreshed $summary")
return (_appState.value as? KiloAppState.Ready) ?: current
}
Expand Down Expand Up @@ -342,7 +355,7 @@ class KiloBackendAppService private constructor(
}

val errors = CopyOnWriteArrayList<LoadError>()
var cfg: Config? = null
var cfg: ConfigDto? = null
var prof: KiloProfile200Response? = null
var notifs: List<KiloNotifications200ResponseInner> = emptyList()
var warns: List<ConfigWarning> = emptyList()
Expand Down Expand Up @@ -553,14 +566,39 @@ class KiloBackendAppService private constructor(
}
}

private suspend fun fetchConfig(): FetchResult<Config> {
val client = connection.appLoadApi
private suspend fun fetchConfig(): FetchResult<ConfigDto> {
val http = connection.apiClient
?: return FetchResult.fail("config", detail = "Not connected")
return try {
FetchResult.ok(client.globalConfigGet())
val request = Request.Builder()
.url("http://127.0.0.1:$port/global/config")
.header("Accept", "application/json")
.get()
.build()
val body = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { cont ->
val call = http.newCall(request)
cont.invokeOnCancellation { call.cancel() }
try {
val text = call.execute().use { response ->
val text = response.body?.string().orEmpty()
if (!response.isSuccessful) {
log.warn("Global config fetch failed: HTTP ${response.code} ${response.message} $text")
throw IllegalStateException("Global config fetch failed: HTTP ${response.code} ${response.message}")
}
text
}
if (cont.isActive) cont.resume(text)
} catch (e: Exception) {
if (cont.isActive) cont.resumeWithException(e)
}
}
}
FetchResult.ok(KiloCliDataParser.parseConfig(body))
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log.warn("Global config fetch failed: ${e.message}", e)
logResponseBody("config", e)
FetchResult.fail("config", e)
}
}
Expand Down Expand Up @@ -644,7 +682,7 @@ class KiloBackendAppService private constructor(
return "${warn.path}: ${warn.message}$detail"
}

private fun configSummary(cfg: Config): String {
private fun configSummary(cfg: ConfigDto): String {
val text = cfg.toString()
return "configChars=${text.length} configHash=${text.hashCode().toUInt().toString(16)}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ internal fun buildKiloCliEnv(
put("KILO_PLATFORM", "jetbrains")
put("KILO_APP_NAME", "kilo-code")
put("KILO_TELEMETRY_LEVEL", if (KiloDevMode.enabled()) "off" else "all")
put("KILO_DISABLE_CLAUDE_CODE", "true")
if (!KiloClaudeCompatSettings.get()) put("KILO_DISABLE_CLAUDE_CODE", "true")
put("KILOCODE_FEATURE", "jetbrains-plugin")
putIfAbsent("KILO_CONFIG_CONTENT", DEFAULT_CONFIG)
ideEnv(log).forEach { entry -> put(entry.key, entry.value) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ai.kilocode.backend.cli

import com.intellij.ide.util.PropertiesComponent

object KiloClaudeCompatSettings {
private const val KEY = "kilo.claudeCodeCompat"

@Volatile
private var fallback = false

fun get(): Boolean {
val props = props()
return props?.getBoolean(KEY, false) ?: fallback
}

fun set(value: Boolean) {
fallback = value
val props = props()
props?.setValue(KEY, value.toString())
}

private fun props(): PropertiesComponent? = runCatching { PropertiesComponent.getInstance() }.getOrNull()
}
Loading
Loading