fix(jetbrains): address agent manager review feedback

This commit is contained in:
kirillk
2026-08-17 09:50:21 -04:00
parent 707b1ca898
commit ede14046da
25 changed files with 62 additions and 44 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
"@kilocode/kilo-jetbrains": minor
---
Add a Terminal button to the Agent Manager worktree header that opens (or focuses) a terminal in the worktree's directory, reusing one terminal tab per worktree. The terminal tab is labelled with the same worktree name shown in the worktree list and editor tab, and it updates when the worktree is renamed or its pull request changes. The worktree header actions now use flat, hoverable toolbar buttons, and the branch-changes badge shows the commits-ahead count and file count with a descriptive tooltip (ahead commits, files changed, and lines added/removed).
Add a Terminal button to the Agent Manager worktree header that opens (or focuses) a terminal in the worktree's directory, reusing one terminal tab per worktree. The terminal tab is labelled with the same worktree name shown in the worktree list and editor tab, and it updates when the worktree is renamed or its pull request changes. The worktree header actions now use flat, hoverable toolbar buttons, and the branch-changes badge shows the changed-file count and lines added/removed, opening the branch diff when clicked.
@@ -165,12 +165,9 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
if (status != GhAvailability.OK) return@parallel null
val out = runGh(Path.of(item.path).normalize(), "pr", "view", item.branch, "--json", "number,state,isDraft,url,title")
if (!out.ok) {
when (prError(out.stderr)) {
GhAvailability.UNAUTH -> status = GhAvailability.UNAUTH
GhAvailability.MISSING -> status = GhAvailability.MISSING
GhAvailability.GIT_MISSING -> status = GhAvailability.GIT_MISSING
GhAvailability.OK -> Unit
}
// prError only ever returns UNAUTH or OK; a missing gh/git binary is already caught
// by the upfront ghAvailable() check before this loop runs.
if (prError(out.stderr) == GhAvailability.UNAUTH) status = GhAvailability.UNAUTH
return@parallel null
}
parsePr(item.path, out.stdout)
@@ -457,7 +454,10 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
internal fun classifyGhError(text: String): GhAvailability {
val msg = text.lowercase()
if (msg.contains("not logged") || msg.contains("gh auth login") || msg.contains("authentication")) return GhAvailability.UNAUTH
if (msg.contains("cannot run program") || msg.contains("no such file") || msg.contains("not found")) return GhAvailability.MISSING
// Only treat process-spawn failures as MISSING. A bare "not found" match would misclassify
// transient gh auth failures (e.g. a GitHub Enterprise 404 or revoked token) as an uninstalled gh;
// scope to spawn/shell signals instead.
if (msg.contains("cannot run program") || msg.contains("no such file") || msg.contains("command not found")) return GhAvailability.MISSING
return GhAvailability.OK
}
@@ -167,5 +167,8 @@ internal fun Content.applyAgentManagerBetaBadge() {
icon = AllIcons.General.Beta
description = KiloBundle.message("sidePanel.mode.agentManager.beta.description")
putUserData(ToolWindow.SHOW_CONTENT_ICON, true)
// TAB_LABEL_ORIENTATION_KEY is @ApiStatus.Experimental and may change or disappear between IDE
// releases; we declare no untilBuild cap. Failure is benign: putUserData no-ops and the Beta
// icon falls back to the left of the tab label.
putUserData(Content.TAB_LABEL_ORIENTATION_KEY, ComponentOrientation.RIGHT_TO_LEFT)
}
@@ -17,6 +17,7 @@ import com.intellij.openapi.components.Service
import fleet.rpc.client.durable
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* App-level service wrapping [ai.kilocode.rpc.KiloWorktreeRpcApi]. Mirrors [ai.kilocode.client.app.KiloWorkspaceService]:
@@ -63,6 +64,18 @@ class KiloWorktreeService internal constructor(
false
}
/**
* Fire-and-forget open on the service scope. EDT click handlers have no thread-bound coroutine
* scope, so they route through here instead of `currentThreadCoroutineScope()`, which throws
* outside progress/blocking contexts.
*/
fun openInBackground(directory: String) {
cs.launch {
val ok = open(directory)
LOG.info("worktree open: backend returned=$ok dir=$directory")
}
}
suspend fun stats(directory: String): WorktreeStatsListDto = try {
call { stats(directory) }
} catch (e: Exception) {
@@ -70,12 +83,12 @@ class KiloWorktreeService internal constructor(
WorktreeStatsListDto()
}
suspend fun ghStatus(directory: String): GhAvailability = try {
call { ghStatus(directory) }
} catch (e: Exception) {
LOG.warn("gh status failed for $directory", e)
GhAvailability.OK
}
/**
* Reports gh availability, or rethrows on RPC/backend failure. Callers ([GhStatusCoordinator])
* distinguish a healthy gh from an unhealthy backend via their own `runCatching` + backoff;
* swallowing errors here would publish a false "gh is fine" and reset that backoff.
*/
suspend fun ghStatus(directory: String): GhAvailability = call { ghStatus(directory) }
suspend fun prStatus(directory: String): WorktreePrListDto = try {
call { prStatus(directory) }
@@ -31,6 +31,7 @@ import ai.kilocode.rpc.dto.SessionDto
import ai.kilocode.rpc.dto.WorktreePrDto
import ai.kilocode.rpc.dto.WorktreeStatsDto
import com.intellij.icons.AllIcons
import com.intellij.ide.ActivityTracker
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
@@ -43,7 +44,6 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.UiDataProvider
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.currentThreadCoroutineScope
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer
@@ -61,8 +61,6 @@ import com.intellij.ui.awt.RelativePoint
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.components.BorderLayoutPanel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.plugins.terminal.TerminalToolWindowFactory
import java.awt.BorderLayout
import java.awt.Color
@@ -266,9 +264,14 @@ class WorktreeSessionEditorPanel(
@RequiresEdt
private fun syncToolbar() {
if (!ApplicationManager.getApplication().isUnitTestMode) return
@Suppress("DEPRECATION")
toolbar.updateActionsImmediately()
// Tests need a synchronous refresh to assert action presentations; production nudges the
// platform's action-update pass instead of the deprecated blocking updateActionsImmediately().
if (ApplicationManager.getApplication().isUnitTestMode) {
@Suppress("DEPRECATION")
toolbar.updateActionsImmediately()
return
}
ActivityTracker.getInstance().inc()
}
@RequiresEdt
@@ -292,11 +295,7 @@ class WorktreeSessionEditorPanel(
}
if (focusExistingFrame(dir)) return
LOG.info("worktree open: no local frame matched, delegating to backend dir=$dir")
currentThreadCoroutineScope().launch(Dispatchers.Default) {
val focused = runCatching { service<KiloWorktreeService>().open(dir) }
focused.onFailure { LOG.warn("worktree open: backend call failed dir=$dir", it) }
focused.onSuccess { LOG.info("worktree open: backend returned=$it dir=$dir") }
}
service<KiloWorktreeService>().openInBackground(dir)
}
/**
@@ -393,7 +393,6 @@ worktree.pr.state.closed=Closed
worktree.pr.tooltip.open=Click to open the pull request in your browser.
worktree.gh.missing.title=GitHub CLI not found
worktree.gh.missing.content=Install gh to show pull request badges for worktrees.
worktree.gh.install=Install
worktree.git.missing.title=Git not found
worktree.git.missing.content=Install Git to show worktree stats and pull request badges.
worktree.gh.unauth.title=GitHub CLI not authorized
@@ -139,7 +139,6 @@ history.error.cloud.delete=لا يمكن حذف جلسات السحابة بعد
worktree.gh.missing.title=لم يتم العثور على GitHub CLI
worktree.gh.missing.content=ثبّت gh لعرض شارات طلبات السحب لفروع العمل.
worktree.gh.install=تثبيت
worktree.git.missing.title=لم يتم العثور على Git
worktree.git.missing.content=ثبّت Git لعرض إحصاءات فروع العمل وشارات طلبات السحب.
worktree.gh.unauth.title=GitHub CLI غير مخوّل
@@ -139,7 +139,6 @@ history.error.cloud.delete=Sesije iz oblaka još uvijek nije moguće brisati
worktree.gh.missing.title=GitHub CLI nije pronađen
worktree.gh.missing.content=Instalirajte gh za prikaz oznaka pull requestova za radna stabla.
worktree.gh.install=Instaliraj
worktree.git.missing.title=Git nije pronađen
worktree.git.missing.content=Instalirajte Git za prikaz statistike radnih stabala i oznaka pull requestova.
worktree.gh.unauth.title=GitHub CLI nije autorizovan
@@ -139,7 +139,6 @@ history.error.cloud.delete=Skysessioner kan endnu ikke slettes
worktree.gh.missing.title=GitHub CLI blev ikke fundet
worktree.gh.missing.content=Installer gh for at vise pull request-badges for worktrees.
worktree.gh.install=Installer
worktree.git.missing.title=Git blev ikke fundet
worktree.git.missing.content=Installer Git for at vise worktree-statistik og pull request-badges.
worktree.gh.unauth.title=GitHub CLI er ikke autoriseret
@@ -139,7 +139,6 @@ history.error.cloud.delete=Cloud-Sitzungen können noch nicht gelöscht werden
worktree.gh.missing.title=GitHub CLI nicht gefunden
worktree.gh.missing.content=Installiere gh, um Pull-Request-Badges für Worktrees anzuzeigen.
worktree.gh.install=Installieren
worktree.git.missing.title=Git nicht gefunden
worktree.git.missing.content=Installiere Git, um Worktree-Statistiken und Pull-Request-Badges anzuzeigen.
worktree.gh.unauth.title=GitHub CLI ist nicht autorisiert
@@ -139,7 +139,6 @@ history.error.cloud.delete=Las sesiones en la nube no se pueden eliminar aún
worktree.gh.missing.title=No se encontró GitHub CLI
worktree.gh.missing.content=Instala gh para mostrar insignias de pull request para los worktrees.
worktree.gh.install=Instalar
worktree.git.missing.title=No se encontró Git
worktree.git.missing.content=Instala Git para mostrar estadísticas de worktrees e insignias de pull request.
worktree.gh.unauth.title=GitHub CLI no está autorizado
@@ -139,7 +139,6 @@ history.error.cloud.delete=Les sessions cloud ne peuvent pas encore être suppri
worktree.gh.missing.title=GitHub CLI introuvable
worktree.gh.missing.content=Installez gh pour afficher les badges de pull request des worktrees.
worktree.gh.install=Installer
worktree.git.missing.title=Git introuvable
worktree.git.missing.content=Installez Git pour afficher les statistiques des worktrees et les badges de pull request.
worktree.gh.unauth.title=GitHub CLI nest pas autorisé
@@ -139,7 +139,6 @@ history.error.cloud.delete=クラウドセッションはまだ削除できま
worktree.gh.missing.title=GitHub CLI が見つかりません
worktree.gh.missing.content=worktree のプルリクエストバッジを表示するには gh をインストールしてください。
worktree.gh.install=インストール
worktree.git.missing.title=Git が見つかりません
worktree.git.missing.content=worktree の統計情報とプルリクエストバッジを表示するには Git をインストールしてください。
worktree.gh.unauth.title=GitHub CLI が認証されていません
@@ -139,7 +139,6 @@ history.error.cloud.delete=클라우드 세션은 아직 삭제할 수 없습니
worktree.gh.missing.title=GitHub CLI를 찾을 수 없음
worktree.gh.missing.content=워크트리의 pull request 배지를 표시하려면 gh를 설치하세요.
worktree.gh.install=설치
worktree.git.missing.title=Git을 찾을 수 없음
worktree.git.missing.content=워크트리 통계와 pull request 배지를 표시하려면 Git을 설치하세요.
worktree.gh.unauth.title=GitHub CLI가 인증되지 않음
@@ -139,7 +139,6 @@ history.error.cloud.delete=Cloudsessies kunnen nog niet worden verwijderd
worktree.gh.missing.title=GitHub CLI niet gevonden
worktree.gh.missing.content=Installeer gh om pull request-badges voor worktrees te tonen.
worktree.gh.install=Installeren
worktree.git.missing.title=Git niet gevonden
worktree.git.missing.content=Installeer Git om worktree-statistieken en pull request-badges te tonen.
worktree.gh.unauth.title=GitHub CLI is niet geautoriseerd
@@ -144,7 +144,6 @@ history.error.cloud.delete=Sky-økter kan ikke slettes ennå
worktree.gh.missing.title=GitHub CLI ble ikke funnet
worktree.gh.missing.content=Installer gh for å vise pull request-merker for worktrees.
worktree.gh.install=Installer
worktree.git.missing.title=Git ble ikke funnet
worktree.git.missing.content=Installer Git for å vise worktree-statistikk og pull request-merker.
worktree.gh.unauth.title=GitHub CLI er ikke autorisert
@@ -144,7 +144,6 @@ history.error.cloud.delete=Sesji w chmurze nie można jeszcze usuwać
worktree.gh.missing.title=Nie znaleziono GitHub CLI
worktree.gh.missing.content=Zainstaluj gh, aby wyświetlać odznaki pull requestów dla worktree.
worktree.gh.install=Zainstaluj
worktree.git.missing.title=Nie znaleziono Git
worktree.git.missing.content=Zainstaluj Git, aby wyświetlać statystyki worktree i odznaki pull requestów.
worktree.gh.unauth.title=GitHub CLI nie jest autoryzowany
@@ -144,7 +144,6 @@ history.error.cloud.delete=As sessões na nuvem ainda não podem ser excluídas
worktree.gh.missing.title=GitHub CLI não encontrado
worktree.gh.missing.content=Instale o gh para mostrar emblemas de pull request para worktrees.
worktree.gh.install=Instalar
worktree.git.missing.title=Git não encontrado
worktree.git.missing.content=Instale o Git para mostrar estatísticas de worktrees e emblemas de pull request.
worktree.gh.unauth.title=GitHub CLI não autorizado
@@ -144,7 +144,6 @@ history.error.cloud.delete=Облачные сессии пока нельзя
worktree.gh.missing.title=GitHub CLI не найден
worktree.gh.missing.content=Установите gh, чтобы показывать значки pull request для worktree.
worktree.gh.install=Установить
worktree.git.missing.title=Git не найден
worktree.git.missing.content=Установите Git, чтобы показывать статистику worktree и значки pull request.
worktree.gh.unauth.title=GitHub CLI не авторизован
@@ -144,7 +144,6 @@ history.error.cloud.delete=ยังไม่สามารถลบเซส
worktree.gh.missing.title=ไม่พบ GitHub CLI
worktree.gh.missing.content=ติดตั้ง gh เพื่อแสดงป้าย pull request สำหรับ worktree
worktree.gh.install=ติดตั้ง
worktree.git.missing.title=ไม่พบ Git
worktree.git.missing.content=ติดตั้ง Git เพื่อแสดงสถิติ worktree และป้าย pull request
worktree.gh.unauth.title=GitHub CLI ยังไม่ได้รับอนุญาต
@@ -144,7 +144,6 @@ history.error.cloud.delete=Bulut oturumları henüz silinemiyor
worktree.gh.missing.title=GitHub CLI bulunamadı
worktree.gh.missing.content=Worktree pull request rozetlerini göstermek için gh yükleyin.
worktree.gh.install=Yükle
worktree.git.missing.title=Git bulunamadı
worktree.git.missing.content=Worktree istatistiklerini ve pull request rozetlerini göstermek için Git yükleyin.
worktree.gh.unauth.title=GitHub CLI yetkilendirilmemiş
@@ -139,7 +139,6 @@ history.error.cloud.delete=Хмарні сесії поки не можна ви
worktree.gh.missing.title=GitHub CLI не знайдено
worktree.gh.missing.content=Установіть gh, щоб показувати значки pull request для worktree.
worktree.gh.install=Установити
worktree.git.missing.title=Git не знайдено
worktree.git.missing.content=Установіть Git, щоб показувати статистику worktree і значки pull request.
worktree.gh.unauth.title=GitHub CLI не авторизовано
@@ -139,7 +139,6 @@ history.error.cloud.delete=云端会话暂无法删除
worktree.gh.missing.title=未找到 GitHub CLI
worktree.gh.missing.content=安装 gh 以显示 worktree 的 pull request 徽章。
worktree.gh.install=安装
worktree.git.missing.title=未找到 Git
worktree.git.missing.content=安装 Git 以显示 worktree 统计信息和 pull request 徽章。
worktree.gh.unauth.title=GitHub CLI 未授权
@@ -139,7 +139,6 @@ history.error.cloud.delete=雲端工作階段暫時無法刪除
worktree.gh.missing.title=找不到 GitHub CLI
worktree.gh.missing.content=安裝 gh 以顯示 worktree 的 pull request 徽章。
worktree.gh.install=安裝
worktree.git.missing.title=找不到 Git
worktree.git.missing.content=安裝 Git 以顯示 worktree 統計資料和 pull request 徽章。
worktree.gh.unauth.title=GitHub CLI 未授權
@@ -79,6 +79,28 @@ class GhStatusCoordinatorTest : BasePlatformTestCase() {
handle.close()
}
fun `test coordinator backs off on backend failure without reporting ok`() {
rpc.ghResult = GhAvailability.UNAUTH
val handle = edtWait { service.attach(project) }
drain()
assertEquals(GhAvailability.UNAUTH, service.current())
assertEquals(1, rpc.ghCalls.size)
// A backend/RPC failure must reach the coordinator's failure path, not be laundered into OK.
rpc.beforeGhStatus = { throw RuntimeException("backend down") }
timers.advanceBy(5_000)
drain()
assertEquals(2, rpc.ghCalls.size)
assertEquals(GhAvailability.UNAUTH, service.current())
// failures>0 now drives exponential backoff instead of the steady FAST cadence.
timers.advanceBy(5_000)
drain()
assertEquals(3, rpc.ghCalls.size)
assertEquals(GhAvailability.UNAUTH, service.current())
handle.close()
}
fun `test coordinator stops polling after detach`() {
val handle = edtWait { service.attach(project) }
drain()