Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fde3671
fix(jetbrains): scroll wide markdown tables instead of cropping them
kirillk Jul 4, 2026
c37d14d
refactor(jetbrains): extract markdown projector
kirillk Jul 4, 2026
166fe23
fix(jetbrains): preserve collapsed subagent views
kirillk Jul 4, 2026
810c65d
Merge branch 'handsomely-aspen' into massive-fontina
kirillk Jul 4, 2026
f3c886b
fix(jetbrains): avoid blocking prompt mention expansion
kirillk Jul 4, 2026
66cef1b
fix(jetbrains): increase todo checklist padding
kirillk Jul 4, 2026
eb59ad6
fix(jetbrains): balance shell tooltip padding
kirillk Jul 4, 2026
6469e9c
fix(jetbrains): make settings list action buttons fully clickable
kirillk Jul 4, 2026
30407a3
fix(jetbrains): improve prompt picker interactions
kirillk Jul 4, 2026
d144129
fix(jetbrains): tighten task tool coverage
kirillk Jul 4, 2026
59baa02
fix(jetbrains): use transcript font for prompt text
kirillk Jul 4, 2026
8361d24
fix(jetbrains): render compaction marker without prompt chrome
kirillk Jul 4, 2026
dafe38f
fix(jetbrains): stabilize task tool body indent
kirillk Jul 4, 2026
6138b10
fix(jetbrains): address transcript review feedback
kirillk Jul 5, 2026
6e388ea
fix(jetbrains): hide prompt floating toolbar
kirillk Jul 5, 2026
bf41013
fix(jetbrains): simplify prompt toolbar suppression
kirillk Jul 6, 2026
e79f4a5
Merge remote-tracking branch 'origin/main' into massive-fontina
kirillk Jul 6, 2026
dd0a632
feat(jetbrains): highlight focused prompt input
kirillk Jul 6, 2026
819cef5
feat(jetbrains): outline focused prompt input
kirillk Jul 6, 2026
5a60c9c
test(jetbrains): cover prompt focus outline
kirillk Jul 6, 2026
04600b6
chore: remove plan file from pr
kirillk Jul 6, 2026
0ae9519
Merge remote-tracking branch 'origin/main' into massive-fontina
kirillk Jul 6, 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
5 changes: 5 additions & 0 deletions .changeset/fix-jetbrains-prompt-submit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Fix prompt submission in JetBrains IDEs when sending messages with file or git-change mentions.
5 changes: 5 additions & 0 deletions .changeset/fix-jetbrains-provider-action-clicks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Fix unreliable clicks on inline action buttons (Connect, OAuth, Disconnect, Enable) in the JetBrains provider, agent, and MCP settings lists so the whole button is clickable.
5 changes: 5 additions & 0 deletions .changeset/focused-jetbrains-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Show a focus outline around the JetBrains prompt input.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-inline-subagents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Show subagent tool activity inline in JetBrains session transcripts.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-picker-popups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Fix JetBrains prompt pickers so reasoning effort opens above the button and expanded model details still allow one-click model selection.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-prompt-floating-toolbar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Hide the JetBrains editor floating toolbar from the Kilo prompt input.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-shell-tooltip-padding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Balance JetBrains shell command tooltip padding when a horizontal scrollbar is present.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-todo-padding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Increase JetBrains todo checklist inner padding.
5 changes: 5 additions & 0 deletions .changeset/jetbrains-transcript-prompt-font.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---

Use the standard transcript font for JetBrains prompt text and custom question responses.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableWithId
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.concurrency.annotations.RequiresEdt
Expand Down Expand Up @@ -369,6 +368,7 @@ class SessionUi(
onEnhance = controller::enhancePrompt,
onMentions = ::mentionParts,
completion = completion,
cs = cs,
)
connection = ConnectionPanel(this, controller)
root.addOverlay(connection) { pane, child ->
Expand Down Expand Up @@ -695,9 +695,9 @@ class SessionUi(
spec.available,
)

private fun mentionParts(text: String): List<PromptPartDto> = runBlockingCancellable {
private suspend fun mentionParts(text: String): List<PromptPartDto> {
val names = MentionAction.ALL.mapTo(mutableSetOf()) { it.name }
promptMentionParts(
return promptMentionParts(
text = text,
directory = workspace.directory,
reserved = names,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class SessionController(
private var creating: CompletableDeferred<String?>? = null
private val childJobs: MutableMap<String, Job> = mutableMapOf()
private val childIds: MutableSet<String> = mutableSetOf()
private val childParts: MutableMap<PartKey, String> = mutableMapOf()
private var sessionLoadState: SessionLoadState = SessionLoadState.Idle
private var recentsState: RecentsState = RecentsState.Idle
private var viewState: SessionControllerEvent.ViewChanged? = null
Expand All @@ -166,8 +167,6 @@ class SessionController(
private var modelTime: Double? = null
private val snapshots = mutableMapOf<PartKey, String>()

private data class PartKey(val messageId: String, val partId: String)

val ready: Boolean get() = model.isReady()
val autoApprove: Boolean get() = KiloPluginSettings.getAutoApprove()
internal val blank: Boolean get() = ref == null && model.isEmpty() && !model.showSession
Expand Down Expand Up @@ -763,12 +762,14 @@ class SessionController(
val session = target.session ?: runCatching { sessions.get(id, directory) }.getOrNull()
val items = sessions.messages(id, directory)
LOG.debug { "${ChatLogSummary.sid(id)} ${ChatLogSummary.history(items)}" }
val discovered = items.flatMap { it.parts }.mapNotNull { childID(it) }.toSet()
val discovered = children(items)
runEdt {
if (disposed) return@runEdt
if (sid != id) return@runEdt
updateModel {
snapshots.clear()
childParts.clear()
childParts.putAll(discovered)
this@SessionController.model.loadHistory(items)
syncHistoryAgent(items)
if (session != null) this@SessionController.model.setSession(session)
Expand All @@ -778,7 +779,7 @@ class SessionController(
runEdt {
if (disposed) return@runEdt
if (sid != id) return@runEdt
for (child in discovered) trackChild(child)
for (child in discovered.values.toSet()) trackChild(child)
showSession()
loaded(!model.isEmpty())
}
Expand Down Expand Up @@ -811,7 +812,7 @@ class SessionController(
val session = sessions.importCloudSession(id, directory)
val items = sessions.messages(session.id, directory)
LOG.debug { "${ChatLogSummary.sid(session.id)} ${ChatLogSummary.history(items)}" }
val discovered = items.flatMap { it.parts }.mapNotNull { childID(it) }.toSet()
val discovered = children(items)
runEdt {
if (disposed) return@runEdt
ref = SessionRef.Local(session)
Expand All @@ -826,8 +827,10 @@ class SessionController(
recoverPending(session.id)
runEdt {
if (disposed) return@runEdt
for (child in discovered) trackChild(child)
subscribeEvents()
childParts.clear()
childParts.putAll(discovered)
for (child in discovered.values.toSet()) trackChild(child)
showSession()
loaded(!model.isEmpty())
}
Expand Down Expand Up @@ -894,7 +897,7 @@ class SessionController(
val job = cs.launch {
try {
sessions.events(child, directory).collect { event ->
if (!isChildPermissionEvent(event, child)) return@collect
if (!isChildEvent(event, child)) return@collect
LOG.debug { "${ChatLogSummary.sid(sid ?: "pending")} kind=child-event child=$child ${ChatLogSummary.eventBody(event)}" }
updates.enqueue(event)
}
Expand All @@ -914,9 +917,32 @@ class SessionController(
assertEdt()
if (!childIds.add(child)) return
subscribeChild(child)
cs.launch { seedChild(child) }
cs.launch { recoverChildPermissions(child) }
}

@RequiresEdt
private fun trackChild(key: PartKey, child: String) {
assertEdt()
childParts[key] = child
trackChild(child)
}

@RequiresEdt
private fun untrackChild(key: PartKey) {
assertEdt()
val child = childParts.remove(key) ?: return
if (child in childParts.values) return
childIds.remove(child)
childJobs.remove(child)?.cancel()
}

@RequiresEdt
private fun untrackChildren(messageId: String) {
assertEdt()
childParts.keys.filter { it.messageId == messageId }.forEach(::untrackChild)
}

@RequiresEdt
private fun cancelSubscriptions() {
assertEdt()
Expand All @@ -925,6 +951,7 @@ class SessionController(
childJobs.values.forEach { it.cancel() }
childJobs.clear()
childIds.clear()
childParts.clear()
}

private suspend fun recoverChildPermissions(child: String) {
Expand All @@ -939,6 +966,7 @@ class SessionController(
val last = toPermission(permissions.last())
runEdt {
if (disposed) return@runEdt
if (child !in childIds) return@runEdt
// Do not overwrite an existing root or other child AwaitingPermission state
if (model.state is SessionState.AwaitingPermission) return@runEdt
updateModel { model.setState(SessionState.AwaitingPermission(last)) }
Expand All @@ -948,6 +976,28 @@ class SessionController(
}
}

private suspend fun seedChild(child: String) {
try {
val items = sessions.messages(child, directory)
runEdt {
if (disposed) return@runEdt
if (child !in childIds) return@runEdt
updateModel {
for (msg in items) {
if (msg.info.role != "assistant") continue
for (part in msg.parts) {
if (part.type == "tool") model.upsertChildTool(child, part, replace = false)
}
}
}
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
LOG.warn("${ChatLogSummary.sid(sid ?: "pending")} kind=child-history child=$child dir=${ChatLogSummary.dir(directory)} failed message=${e.message}", e)
}
}

/** Rehydrate pending permissions/questions and current session status after history load. */
private suspend fun recoverPending(id: String) {
try {
Expand Down Expand Up @@ -1026,10 +1076,17 @@ class SessionController(
}

is ChatEventDto.PartUpdated -> {
if (childIds.contains(event.sessionID)) {
if (event.part.type == "tool") model.upsertChildTool(event.sessionID, event.part)
return
}
partType = event.part.type
tool = event.part.tool
val key = PartKey(event.part.messageID, event.part.id)
val prev = content(event.part.messageID, event.part.id)
val child = childID(event.part)
val old = childParts[key]
if (old != null && old != child) untrackChild(key)
model.updateContent(event.part.messageID, event.part)
val next = content(event.part.messageID, event.part.id)
if (next != null && next != prev) {
Expand All @@ -1040,7 +1097,7 @@ class SessionController(
if (model.state is SessionState.Busy) {
model.setState(SessionState.Busy(status()))
}
childID(event.part)?.let { child -> trackChild(child) }
if (child != null) trackChild(key, child)
}

is ChatEventDto.PartDelta -> {
Expand All @@ -1051,7 +1108,13 @@ class SessionController(
}

is ChatEventDto.PartRemoved -> {
snapshots.remove(PartKey(event.messageID, event.partID))
if (childIds.contains(event.sessionID)) {
model.removeChildTool(event.sessionID, event.partID)
return
}
val key = PartKey(event.messageID, event.partID)
snapshots.remove(key)
untrackChild(key)
model.removeContent(event.messageID, event.partID)
}

Expand Down Expand Up @@ -1086,6 +1149,7 @@ class SessionController(

is ChatEventDto.MessageRemoved -> {
snapshots.keys.removeAll { it.messageId == event.messageID }
untrackChildren(event.messageID)
model.removeMessage(event.messageID)
}

Expand Down Expand Up @@ -1918,8 +1982,20 @@ private fun childID(part: PartDto): String? {
return part.metadata["sessionId"]
}

/** Returns true when [event] is a permission event for [child] (used by child subscriptions). */
private fun isChildPermissionEvent(event: ChatEventDto, child: String): Boolean = when (event) {
private data class PartKey(val messageId: String, val partId: String)

private fun children(items: List<MessageWithPartsDto>): Map<PartKey, String> = buildMap {
for (msg in items) {
for (part in msg.parts) {
childID(part)?.let { put(PartKey(msg.info.id, part.id), it) }
}
}
}

/** Returns true when [event] should be routed from a child subscription. */
private fun isChildEvent(event: ChatEventDto, child: String): Boolean = when (event) {
is ChatEventDto.PartUpdated -> event.sessionID == child
is ChatEventDto.PartRemoved -> event.sessionID == child
is ChatEventDto.PermissionAsked -> event.sessionID == child
is ChatEventDto.PermissionReplied -> event.sessionID == child
else -> false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class Tool(id: String, val name: String, var kind: ToolKind) : Content(id) {
var title: String? = null
var input: Map<String, String> = emptyMap()
var metadata: Map<String, String> = emptyMap()
var childSessionId: String? = null
var childTools: List<Tool> = emptyList()
var output: String? = null
var error: String? = null
var time: PartTimeDto? = null
Expand Down
Loading
Loading