feat(jetbrains): surface unsupported workspace in session banner

Show unsupported workspace directories (Dev Container / WSL / invalid
virtual path) through the standard in-session connection banner instead
of a stuck "Loading…" state. The banner includes the workspace path and
two clear, localized options: reopen in the container/WSL via JetBrains
Gateway, or open from the local filesystem. Expanded error details now
fit the full text, capped to the available transcript height.

Remove the dev-only kilo.dev.forceUnsupportedWorkspace override used to
reproduce the state during development.
This commit is contained in:
kirillk
2026-08-20 11:52:50 -04:00
parent a4ab4325c3
commit 55fb0f2010
34 changed files with 712 additions and 53 deletions
@@ -0,0 +1,103 @@
# JetBrains: graceful "Dev Container not supported" workspace notice
## Goal
When a JetBrains project is opened so that its directory is a **local-IDE + virtual (IJent) path** — e.g. `/$devcontainer.ij/<id>@…podman.sock/…` (the "Model 2" case) — the host-side Kilo CLI cannot resolve the directory, so agent resolution returns HTTP 500 and the workspace fails to load. Today this surfaces as a generic red "Workspace loading failed" banner with a futile "Try again".
Replace that with a clear, non-error notice that:
- explains Kilo can't access the project because it's opened through a Dev Container / remote virtual filesystem, and
- recommends running Kilo **inside the container** via JetBrains Remote Development (backend-in-container, the validated "Model 1" flow) as the preferred way, and
- offers a "Learn more" link.
Detection short-circuits workspace load **before** the 3× `/agent` 500 retries.
## Background (verified in code)
- Backend workspace load: `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt` (`load()` fetches agents/providers/commands/skills; failure → `KiloWorkspaceState.Error`).
- Backend state model: `.../backend/workspace/KiloWorkspaceState.kt` (`Pending/Loading/Ready/Error`).
- RPC DTO: `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/KiloWorkspaceStateDto.kt` (`KiloWorkspaceStatusDto = PENDING/LOADING/READY/ERROR`).
- DTO mapping (exhaustive `when` over sealed state): `.../backend/rpc/KiloWorkspaceRpcApiImpl.kt` `dto(state)` (~line 493). Directory resolution `resolveProjectDirectory` returns `project.basePath`; `localConfig` (~line 343) already throws `InvalidPathException` for IJent paths.
- Frontend mapping to UI: `.../frontend/.../session/controller/SessionController.kt` `resolveConnectionState()` (~line 2270) maps `workspace.status == ERROR``ConnectionChanged.ShowError`; `retryConnection` reloads the workspace when status is ERROR (~line 622).
- Connection banner UI: `.../frontend/.../session/ui/ConnectionPanel.kt` (red/warning label + expandable details + "Try again" `ActionLink`). Events defined in `.../session/controller/SessionControllerEvent.kt` (`ConnectionChanged.{Hide,ShowConnecting,ShowDownloading,ShowError,ShowWarning}`).
- Strings: `.../frontend/src/main/resources/messages/KiloBundle.properties` (`session.connection.*`). Only the base bundle needs new keys; other locales fall back.
- This builds on the committed fix (`fix(jetbrains): don't surface workspace fetch failures as IDE errors`); genuine 500s from *real* directories keep the existing Error path.
## Design decisions
1. **Representation: dedicated non-error state (recommended).** Add a new workspace status rather than reusing `ERROR`, so the UI is informational (not red), retry is suppressed, and there is no 500/log/retry spam.
2. **Detection lives in the backend workspace load path**, keyed on the directory string, run before any fetch. Global app load stays `READY` (providers/config are global and unaffected).
3. **Detection signals (low false-positive):**
- Directory contains the marker `/$devcontainer.ij/`, or
- starts with a WSL root `\\wsl$\` or `\\wsl.localhost\`, or
- `java.nio.file.Path.of(directory)` (or normalize) throws `InvalidPathException`.
Do **not** trigger solely on "path doesn't exist" (avoids false positives on transient FS states / real paths). Real container paths (Model 1, e.g. `/workspaces/podman`) and normal local paths never match.
4. **Retry:** hidden for the unsupported state (deterministic; reload would re-detect). The workspace re-evaluates naturally when the directory changes (new workspace instance).
5. **Link:** a single "Learn more" hyperlink. Default target `https://kilo.ai/docs/jetbrains/dev-containers` (see Open items — confirm/replace). Keep the URL as one constant so it's trivial to change.
6. **Localization:** add keys to base `KiloBundle.properties` only.
## Implementation tasks (ordered)
### 1. Shared DTO
- `shared/.../rpc/dto/KiloWorkspaceStateDto.kt`: add `UNSUPPORTED` to `KiloWorkspaceStatusDto`. Reuse the existing `error: String?` field to carry a short reason code/message (no new field required); keep `errors` empty for this state.
### 2. Backend state + detection
- `backend/.../workspace/KiloWorkspaceState.kt`: add `data class Unsupported(val reason: String) : KiloWorkspaceState()`.
- Add a small pure, unit-testable helper (single-word-friendly names), e.g. `RemoteDirectory.detect(directory: String): String?` returning a reason string when the directory is an unsupported virtual/IJent path, else `null`. Place it in `backend/.../workspace/` (backend-owned; no `kilocode_change` marker needed — new Kilo file in the Kilo plugin).
- `backend/.../workspace/KiloBackendWorkspace.kt`: at the very start of `load()`, if `RemoteDirectory.detect(directory) != null`, set `_state.value = KiloWorkspaceState.Unsupported(reason)`, log a single `info`/`warn` line (not `error`), and return without fetching. Ensure no fetch/retry runs.
### 3. Backend RPC mapping
- `backend/.../rpc/KiloWorkspaceRpcApiImpl.kt` `dto(state)`: add branch `is KiloWorkspaceState.Unsupported -> KiloWorkspaceStateDto(status = UNSUPPORTED, error = state.reason)`.
### 4. Frontend event + controller
- `frontend/.../session/controller/SessionControllerEvent.kt`: add `data class ShowNotice(val summary: String, val detail: String?, val learnMoreUrl: String? = null) : ConnectionChanged()`.
- `frontend/.../session/controller/SessionController.kt`:
- `resolveConnectionState()`: add a branch for `workspace.status == KiloWorkspaceStatusDto.UNSUPPORTED` (place before the generic ERROR branch) returning `ShowNotice(summary=…, detail=…, learnMoreUrl=…)` using new bundle keys.
- `retryConnection()` / retry path (~line 622): do **not** call `workspace.reload()` when status is `UNSUPPORTED`.
- Confirm `WorkspaceChanged` handling (~line 933) already no-ops for non-READY (it does).
### 5. Frontend rendering
- `frontend/.../session/ui/ConnectionPanel.kt`: handle `ConnectionChanged.ShowNotice`:
- Info styling (secondary/label foreground, not `errorLabelForeground`).
- Show the guidance text; keep `detail` in the expandable area if used.
- Render a "Learn more" link via platform `HyperlinkLabel`/`ActionLink` that opens `learnMoreUrl` with `BrowserUtil.browse(url)`.
- Hide the "Try again" retry link for this event.
### 6. Strings
- `frontend/.../messages/KiloBundle.properties`: add keys, e.g.:
- `session.connection.notice.devcontainer.summary=Kilo can't access this Dev Container project`
- `session.connection.notice.devcontainer.detail=This project is opened through a Dev Container/remote virtual filesystem that the Kilo runtime on your machine can't reach. Run Kilo inside the container using JetBrains Remote Development (the IDE backend runs in the container) — that's the recommended way. Local projects also work.`
- `session.connection.notice.learnMore=Learn more`
- Store the docs URL as a Kotlin constant referenced by the controller (single source), not in the bundle.
### 7. Tests
- Backend `backend/src/test/.../workspace/KiloBackendWorkspaceTest.kt`:
- Given directory `/$devcontainer.ij/abc@…podman.sock/IdeaProjects/x`, workspace state becomes `Unsupported`; assert **no** `/agent` request hit the mock CLI (via `MockCliServer` request log), no retries, and no `ERROR`/500 log lines.
- Add a `\\wsl$\Ubuntu\home\x` case and an `InvalidPathException`-triggering case.
- Negative: a normal local dir and a real `/workspaces/...`-style dir still load normally (existing tests cover normal load).
- Add/extend a mapping assertion: `Unsupported``KiloWorkspaceStateDto(status=UNSUPPORTED, error=reason)`.
- Frontend `frontend/src/test/.../session/ui/ConnectionPanelTest.kt`: `ShowNotice` renders info label (not error color), shows the summary, exposes the "Learn more" link, and hides retry.
- Frontend controller test (`session/controller/…`): `KiloWorkspaceStatusDto.UNSUPPORTED` state produces a `ConnectionChanged.ShowNotice`, and `retryConnection()` does not call `workspace.reload()` for UNSUPPORTED.
### 8. Changeset
- Add `.changeset/<slug>.md` (`"@kilocode/kilo-jetbrains": patch`) describing the user-facing behavior: "Show a clear notice (with guidance to run in a Dev Container) instead of a generic error when a project is opened through a Dev Container/remote virtual filesystem Kilo can't access."
## Out of scope
- Actually supporting Model 2 (running the CLI in-container via Eel/IJent, port forwarding, path translation). This plan only adds graceful communication.
- Hardening every directory-scoped RPC (`models`, file search, git) for virtual paths; they already fail soft. The banner communicates the root cause.
## Failure modes / edge cases
- **False positive** on a legitimate directory: mitigated by marker-only + `InvalidPathException` detection (not "not exists").
- **Model 1 unaffected:** backend runs in the container, directory is real (`/workspaces/...`), markers don't match → normal load.
- **App stays READY:** only the workspace is Unsupported; global providers/config still load, so the rest of the UI (settings, providers) remains usable.
- **New enum value:** update the exhaustive `when` in backend `dto()` (compile-enforced). Frontend status checks are equality-based; add the UNSUPPORTED branch in `resolveConnectionState` and confirm no other exhaustive `when(status)` needs a branch (grep `KiloWorkspaceStatusDto.`).
## Validation
- From `packages/kilo-jetbrains/`: `./gradlew :backend:test --tests ai.kilocode.backend.workspace.KiloBackendWorkspaceTest` and the new frontend tests (`./gradlew :frontend:test --tests …ConnectionPanelTest` and the controller test).
- From `packages/kilo-jetbrains/`: `bun run typecheck` (or `./gradlew typecheck`) and `./gradlew test`.
- Run inspection "Plugin DevKit | Code | Frontend and Backend API Usage" since split-mode code (shared DTO + frontend event) changes.
- Manual (optional): reproduce Model 2 on Linux+rootless Podman (local IDE + IJent path) and confirm the info banner + link appears instead of the red error, with no IDE internal-error popup.
## Open items (non-blocking; recommended defaults chosen)
1. **Docs URL** for "Learn more": default `https://kilo.ai/docs/jetbrains/dev-containers`. Confirm the final path or point to an existing page; may require creating that docs page (in `packages/kilo-docs/`) separately. If source URLs under `packages/kilo-vscode`/`opencode` change this doesn't apply, but if a docs page is added, run `bun run script/extract-source-links.ts` only if a tracked source URL changes.
2. **Optional telemetry:** capture a "Dev Container Unsupported Shown" event via the existing `capture(...)` pattern in `SessionController` when the notice is first shown. Recommended: include it; low cost.
3. **Copy review:** finalize the exact wording of the summary/detail strings.
@@ -0,0 +1,177 @@
# JetBrains: surface "Dev Container unsupported" via the existing turnoutcome card
## Goal
When a JetBrains project is opened through a localIDE + virtual (IJent) path (Model 2 —
`/$devcontainer.ij/…podman.sock/…`, `\\wsl$\…`, or an `InvalidPathException` path), the host CLI
can't resolve the directory and the workspace can't load. Communicate this **using the existing
inchat outcome card** (`SessionOutcomeView`), shown **immediately** on open — not with a new
ConnectionPanel banner. Extract the outcome card so it renders both inside a session transcript
(existing use) and standalone for this workspacelevel condition.
Decision from planning: **show immediately when the workspace is `UNSUPPORTED`, and extract the
common outcomecard UI so it is reused in the session context and outside it.**
## Precondition (blocking)
- The outcome UI (`model/TurnOutcome.kt`, `views/SessionOutcomeView.kt`, `SessionState.TurnEnded`,
`SessionMessageListPanel.outcome` wiring, `SessionUi.outcome`) exists on **`origin/main`** (commits
`b1a8893f14`, `0116b63641`, `58b6edd04f`, `5ad8db8a6d`) but **NOT** on this branch
(`investigate-podman-container-crash`, ~176 commits behind main).
- **Task 1 must be merging/rebasing `origin/main` into this branch** so the outcome UI is present.
Cherrypicking just the four commits is a fallback but rebase/merge is required before the PR
merges anyway. Do this first; reconcile the existing partial work (below) during the merge.
## What already exists on this branch (from earlier work) and how to reconcile
- **Backend (KEEP):** `KiloWorkspaceStatusDto.UNSUPPORTED`, `KiloWorkspaceState.Unsupported(reason)`,
`RemoteDirectory.detect(...)` shortcircuit at the top of `KiloBackendWorkspace.load()`, `dto()`
mapping branch, the `kilo.dev.forceUnsupportedWorkspace` dev flag, and their tests. No change.
- **Frontend (REPLACE):** the `ConnectionChanged.ShowNotice` event + `ConnectionPanel.showNotice`/
"Learn more" link + `resolveConnectionState` UNSUPPORTED→ShowNotice branch + `session.connection.notice.*`
bundle keys were the "new way to communicate" the user rejected. These must be removed/repurposed.
## Design
1. **Backend stays the source of truth.** Workspace load shortcircuits to `Unsupported(reason)`
before any fetch (already implemented); app stays `READY`; DTO carries `status = UNSUPPORTED`,
`error = reason` (`devcontainer_virtual_filesystem` | `wsl_virtual_filesystem` | `invalid_virtual_path`).
2. **Reuse the outcome card, don't invent UI.** `SessionOutcomeView` (a `DialogView`/`SessionView`
showing header icon + title + description, plus an optional scrollable error body and a
DialogView action footer) is the single card class. It is already constructed in `SessionUi` and
injected into `SessionMessageListPanel`; it does not depend on the transcript, so it can be reused
standalone. Add one small method for the informational/notice case.
3. **Show immediately, no session required.** The transcript body only renders when
`model.showSession == true`. For the UNSUPPORTED case there is no session, so route a dedicated
body (a standalone `SessionOutcomeView`) as the session content the moment the workspace is
UNSUPPORTED, taking precedence over the empty/recents view.
4. **No connection banner for this case.** `resolveConnectionState()` returns `Hide` for UNSUPPORTED
(connection is healthy); retry ignores UNSUPPORTED.
## Implementation tasks (ordered)
### 1. Bring in the outcome UI
- Merge/rebase `origin/main` into this branch. Verify `SessionOutcomeView`, `TurnOutcome`,
`SessionState.TurnEnded`, and the `SessionMessageListPanel.outcome`/`SessionUi.outcome` wiring are
present. Keep backend UNSUPPORTED work; drop the ConnectionPanel notice work (tasks 23).
### 2. Remove the ConnectionPanel notice approach
- `frontend/.../session/controller/SessionControllerEvent.kt`: delete `ConnectionChanged.ShowNotice`.
- `frontend/.../session/ui/ConnectionPanel.kt`: remove `showNotice`, the `learn` ActionLink, the
`actions` `Stack`, the `url` field, and `learnVisible()/learnText()`; restore `retry` directly at
`BorderLayout.EAST`; remove the `ShowNotice` branch in `onEvent` and the `BrowserUtil`/`Stack` imports.
- `frontend/.../session/controller/SessionController.kt`:
- `resolveConnectionState()`: change the `workspace.status == UNSUPPORTED` branch to return
`ConnectionChanged.Hide` (place before the READY/warning branches). This prevents the perpetual
"Loading…" that would otherwise occur because `workspace != READY`.
- `retryConnection()`: keep the guard that returns early (no `workspace.reload()`) when
`workspace.status == UNSUPPORTED`.
- `setConnectionTargetState()`: remove the `ShowNotice` immediatestate branch.
- `frontend/.../messages/KiloBundle.properties`: remove `session.connection.notice.*` keys.
### 3. Extract/extend the reusable outcome card
- `frontend/.../session/views/SessionOutcomeView.kt`: add an EDT method that reuses the existing
card rendering for an informational notice, e.g.:
`fun showNotice(title: String, description: String, tone: OutcomeTone, actions: List<DialogView.Action> = emptyList())`
— sets header icon (WARNING for informational), `setHeader(title, description)`, `setContent(null)`,
`setActions(actions)`, `isVisible = true`, `refresh()`. This reuses the same visual card the
transcript uses; it is not a new surface. Confirm `DialogView` exposes `setActions`/`Action` (it
does — used by `LoginRequiredView`/`RevertBanner`).
- Do not fork the card; the same `SessionOutcomeView` class is used in the transcript and standalone.
### 4. Route the standalone card in `SessionUi`
- `frontend/.../session/SessionUi.kt`:
- Own a standalone `SessionOutcomeView` for the workspace notice (separate instance from the
transcript's `outcome`), plus a body container following the `blankBody`/`progressBody` pattern,
e.g. `unsupportedBody` hosting the standalone view. Register it with `applyStyle`.
- `body(state)`: at the top, if `controller.model.workspace.status == KiloWorkspaceStatusDto.UNSUPPORTED`,
return `unsupportedBody` (and populate the card via `showNotice(...)` mapping the workspace `error`
reason to copy). This wins over `showSession`/empty/progress.
- React to workspace changes: in the `SessionControllerEvent.WorkspaceChanged` handler (and on
the new `ShowUnsupported` view event, task 5), reevaluate the body and populate the card. Keep
`prompt.setReady(controller.model.isReady())` — prompt stays disabled since `isReady()` is false
when workspace ≠ READY, so the card explains why input is unavailable.
### 5. Controller view routing for the unsupported case
- `frontend/.../session/controller/SessionControllerEvent.kt`: add
`data class ShowUnsupported(val reason: String) : ViewChanged()` with a stable `toString()`.
- `frontend/.../session/controller/SessionController.kt`:
- Add a precedence check so that when `model.workspace.status == UNSUPPORTED` (app READY), the
controller emits `ViewChanged.ShowUnsupported(reason)` via `setControllerViewState(...)` instead
of `ShowRecents`/`ShowSession`. Gate `canUseRecents()` (and the recents refresh at ~line 992 /
`refreshRecents`) to return false when UNSUPPORTED so recents don't clobber the notice.
- Handle `ShowUnsupported` in `setControllerViewState` similarly to `ShowRecents`
(e.g. `hideAccountOverlay()` or leave account overlay untouched; do not set `model.showSession`).
- `SessionUi` handles `ViewChanged.ShowUnsupported` by clearing `empty`, populating the standalone
`SessionOutcomeView` from `reason`, and `scroll.show(unsupportedBody)`.
### 6. Copy / content
- `frontend/.../messages/KiloBundle.properties`: add notice copy reused for all virtualfs reasons
(single title + description is sufficient; the recommendation is the same), e.g.:
- `session.unsupported.devcontainer.title=Kilo can't access this Dev Container project`
- `session.unsupported.devcontainer.description=This project is opened through a Dev Container or remote virtual filesystem that the Kilo runtime on your machine can't reach. Run Kilo inside the container using JetBrains Remote Development, where the IDE backend runs in the container. Local projects also work.`
- Optional: `session.unsupported.learnMore=Learn more`
- Note: noargument bundle values must use a single apostrophe (`can't`), not `''` (MessageFormat
only collapses `''` when args are passed).
- Keep the docs URL as one Kotlin constant (reuse the existing `DEVCONTAINER_URL`,
default `https://kilo.ai/docs/jetbrains/dev-containers`). If a "Learn more" action is included,
wire it as a `DialogView.Action` that calls `BrowserUtil.browse(url)`.
- Reason→copy mapping: map all three reasons to the same devcontainer copy for now (a `when(reason)`
helper), so wsl/invalid paths also get a clear message. Tone = `OutcomeTone.WARNING` (informational).
### 7. Tests
- Backend (unchanged, keep passing where the env allows): `RemoteDirectoryTest` (pure, incl. forced
flag), `KiloBackendWorkspaceTest` UNSUPPORTED cases (no `/agent` fetch), `KiloWorkspaceRpcApiImplTest`
mapping. Note: the full backend suite can't run in this worktree because the fakeCLI `connect()`
helper times out here (environmental); `RemoteDirectoryTest` runs fine and should be the primary
backend guard.
- Frontend:
- Remove the obsolete `ConnectionPanelTest` notice test and the `ConnectionDelayTest`
unsupportednotice/retry tests. Replace with: UNSUPPORTED makes `resolveConnectionState` resolve
to `Hide` (no connection banner), and `retryConnection()` does not call `projectRpc.reload`.
- `SessionOutcomeViewTest`: add a case for the new `showNotice(...)` — asserts header icon/title/
description render and (if included) the Learnmore action is present.
- Controller test: workspace `UNSUPPORTED` emits `ViewChanged.ShowUnsupported(reason)` and does
**not** emit `ShowRecents`/`ShowSession`; `canUseRecents()` is false.
- SessionUi/body test (or `SessionMessageListPanel`style test): when workspace is UNSUPPORTED the
standalone outcome card body is shown with the devcontainer title/description.
### 8. Changeset
- Update the existing `.changeset/jetbrains-devcontainer-notice.md` (`"@kilocode/kilo-jetbrains": patch`)
to describe the final behavior: "When a JetBrains project is opened through a Dev Container / remote
virtual filesystem Kilo can't access, show a clear inchat notice (with guidance to run Kilo inside
the container) instead of a generic loading/error state."
## Out of scope
- Actually supporting Model 2 (running the CLI incontainer via Eel/IJent, path translation). This is
communicationonly.
- Reworking the transcript's existing failed/interrupted turn outcomes.
## Risks / edge cases
- **Merge scope:** rebasing ~176 commits may conflict on `SessionController.kt`/`ConnectionPanel.kt`/
`SessionUi.kt`. Reverting the ConnectionPanel notice (task 2) reduces overlap with main's outcome
changes; do task 2 as part of resolving conflicts.
- **Statemachine hygiene:** drive the notice from **workspace status** (view routing), not by
faking a persession `SessionState`, to avoid corrupting the session state machine when there is no
session.
- **Body precedence:** ensure UNSUPPORTED beats empty/recents/progress and that recents refresh does
not overwrite it (`canUseRecents()` guard).
- **App stays READY:** global providers/config still load; only the workspace is Unsupported, so the
rest of the UI (settings/providers) remains usable and no red connection error appears.
- **Manual repro:** `-Pkilo.dev.forceUnsupportedWorkspace=true` on a dev IDE run forces every
workspace into UNSUPPORTED to exercise the card without a real IJent path.
## Validation
- From `packages/kilo-jetbrains/`: `./gradlew :backend:test --tests ai.kilocode.backend.workspace.RemoteDirectoryTest`
and the frontend tests `./gradlew :frontend:test --tests …SessionOutcomeViewTest --tests …ConnectionDelayTest`
plus the new controller/body tests. (Use modulescoped `:backend:`/`:frontend:` task filters.)
- `./gradlew typecheck` and, where the env permits, `./gradlew test`.
- Run inspection "Plugin DevKit | Code | Frontend and Backend API Usage" (shared DTO + new frontend
view event).
- Manual: launch split mode with `-Pkilo.dev.forceUnsupportedWorkspace=true`; confirm the inchat
outcome card appears immediately, the prompt is disabled, and no red connection banner shows.
## Open items
- Confirm/replace the "Learn more" docs URL (`https://kilo.ai/docs/jetbrains/dev-containers`) and
whether to include the Learnmore action at all (recommended: include it as a `DialogView.Action`).
- Final copy review for the notice title/description.
@@ -0,0 +1,149 @@
# Show unsupported workspace via the standard session error banner + fit-to-transcript error height (JetBrains)
## Goal
1. When a JetBrains workspace directory is unsupported for the host-side CLI
runtime (devcontainer / WSL / invalid virtual path), surface it through the
existing in-session connection banner (`ConnectionPanel`) instead of silently
showing "Loading…" forever. It gets the **same** recovery options as other
connection errors (Try again → Retry / Restart / Reinstall Core).
2. Change the expanded error/detail area so it **fits the whole detail text**,
capped to the **available height of the session transcript area** (instead of
the current fixed 10-line cap). This becomes the default behavior for **all**
connection error/warning banners, not just unsupported.
## Background / Current State
- Detection already works end to end. `RemoteDirectory.detect()` returns a reason
code and `KiloBackendWorkspace` sets `KiloWorkspaceState.Unsupported(reason)`,
mapped to `KiloWorkspaceStateDto(status = UNSUPPORTED, error = reason)`
(`KiloWorkspaceRpcApiImpl.kt:507`). Reason string is in the DTO `error` field.
Reason codes: `devcontainer_virtual_filesystem`, `wsl_virtual_filesystem`,
`invalid_virtual_path`.
- The DTO reaches `SessionModel.workspace`; `syncConnectionState()` already runs
on every `WorkspaceChanged` (`SessionController.kt:982-984`).
- **Gap:** `SessionController.resolveConnectionState()` (`SessionController.kt:2353-2393`)
has no `UNSUPPORTED` branch, so it falls through to `ShowConnecting`
(line 2392) → banner reads "Loading…" indefinitely.
- Banner renderer is `ConnectionPanel` (`session/ui/ConnectionPanel.kt`), driven
by `ConnectionChanged.ShowError/ShowWarning/…`. Its "Try again" link opens a
popup with Retry / Restart / Reinstall (`recoveryGroup()`).
- Height cap today: `DETAILS_LINES = 10` (line 45). `scrollHeight()` coerces the
row count to `1..DETAILS_LINES` (line 304), `getPreferredSize()` uses it
(line 295-301), and `maxExpandedHeight()` (line 345) exposes the fixed cap.
- Overlay placement: `SessionUi.kt:442-452` anchors the banner just above the
prompt using `child.preferredSize.height` as the banner height; bottom edge is
`promptTop - gap`, and it grows upward.
All touched files are Kilo-owned JetBrains frontend paths — no `kilocode_change`
markers required. No backend / shared DTO changes needed.
## Design Decisions
- **Unsupported reuses the standard `ShowError` path unchanged** — same red
banner, same "Try again" popup (Retry / Restart / Reinstall). No new event
type and no `retry` flag. (Reverses the earlier "hide retry" idea per user.)
- **Detail area fits the full text, capped to available transcript height.**
Remove the fixed 10-line cap in `ConnectionPanel` so preferred height reflects
the whole detail text; clamp the rendered banner height to the transcript space
above the prompt in the overlay layout (where pane geometry is known). The
existing internal `JBScrollPane` (`VERTICAL_SCROLLBAR_AS_NEEDED`) scrolls the
overflow. This applies to every error/warning banner uniformly.
## Implementation Tasks
1. **`SessionController.resolveConnectionState()`** (`SessionController.kt:2353`)
- Add a branch for `workspace.status == KiloWorkspaceStatusDto.UNSUPPORTED`
(place it next to the workspace `ERROR` branch, before the READY branches):
- summary = `KiloBundle.message("session.connection.unsupported")`
- detail = reason mapped to a localized string (helper below), falling back
to the raw `workspace.error`
- `source = "workspace"` (retry link shows by default; no flag change)
- Add a private helper (single-word name, e.g. `unsupported`) mapping the
`workspace.error` reason code to a bundle string:
- `devcontainer_virtual_filesystem``session.connection.unsupported.devcontainer`
- `wsl_virtual_filesystem``session.connection.unsupported.wsl`
- `invalid_virtual_path``session.connection.unsupported.invalid`
- else → `session.connection.unsupported.unknown` (or raw reason)
2. **`KiloBundle.properties`** (`resources/messages/`, after line 17)
- Add (finalize exact copy during implementation):
- `session.connection.unsupported=Workspace not supported`
- `session.connection.unsupported.devcontainer=Dev Container virtual filesystem paths can't be reached by the host-side Kilo runtime.`
- `session.connection.unsupported.wsl=WSL virtual filesystem paths aren't supported by the host-side Kilo runtime.`
- `session.connection.unsupported.invalid=This workspace path can't be resolved on the local filesystem.`
- `session.connection.unsupported.unknown=This workspace isn't supported by the host-side Kilo runtime.`
3. **`ConnectionPanel.kt`** (`session/ui/`) — remove the fixed cap so details fit
the whole text:
- Delete `DETAILS_LINES` (line 45) usage in `scrollHeight()` (line 303-306):
compute rows from the full logical line count (`coerceAtLeast(1)`), no upper
bound. `getPreferredSize()` (line 295-301) then reports the full detail
height when expanded.
- Remove `maxExpandedHeight()` (line 345) or repurpose it; it encodes the
10-line cap and is only used by the outgoing test.
- Leave the `JBScrollPane` policies as-is so overflow scrolls when the overlay
clamps the banner shorter than preferred.
- Note (known limitation, keep behavior parity): row count uses logical lines,
not wrapped visual lines, so a wrapped long line may under-estimate height;
the scrollbar still covers overflow. Optional follow-up only.
4. **`SessionUi.kt`** overlay layout for `connection` (lines 442-452) — clamp the
banner height to the transcript area above the prompt:
- `full = child.preferredSize.height`
- `avail = (point.y - gap).coerceAtLeast(0)` (space from pane top to just above
the prompt)
- `h = full.coerceAtMost(avail)`
- Rectangle: `x = point.x + gap`, `y = point.y - h - gap`,
`width = (prompt.width - gap*2).coerceAtLeast(0)`, `height = h`
- This keeps the bottom anchored at `promptTop - gap` (unchanged) while
preventing the top from overflowing above the transcript region; the panel's
internal scroll pane handles the remainder. Applies to every banner state.
## Tests
- **`ConnectionDelayTest.kt`** (`session/controller/`): add a test mirroring
`test persistent workspace error is delayed` — set
`projectRpc.state.value = KiloWorkspaceStateDto(status = UNSUPPORTED, error = "wsl_virtual_filesystem")`,
assert a `ShowError` with summary "Workspace not supported", the mapped WSL
detail, and `source == "workspace"`; assert it no longer resolves to
`ShowConnecting`.
- **`ConnectionPanelTest.kt`** (`session/ui/`):
- Replace `test expanded details height is capped at ten lines` (lines 140-150)
with a test asserting the expanded preferred height grows with the full text
(e.g. 30 lines yields a preferred height clearly larger than the old
10-line height / a computed full-text height), i.e. no fixed cap.
- Optionally add a small test that an unsupported-style `ShowError` still shows
the retry link and uses the Core recovery group (parity with existing
`test retry popup group uses core recovery actions`).
- **`SessionUiLayoutTest.kt`**: add a test that with a large detail body and a
constrained root/pane height, the expanded banner is clamped to the transcript
area — `connection.y >= 0` (does not overflow above the transcript), bottom
still anchored at `promptTop - gap`, `detailsVisible()` true, and the internal
`JBScrollPane` shows/needs its vertical scrollbar. Reuse the anchoring
assertions from `test expanded connection panel remains anchored above prompt`
(lines 261-276).
## Validation
From `packages/kilo-jetbrains/`:
- `./gradlew typecheck`
- `./gradlew test` (or targeted: `ConnectionPanelTest`, `ConnectionDelayTest`,
`SessionUiLayoutTest`)
Requires Java 21; only check Java if Gradle fails with a Java-version error.
## Risks / Notes
- Removing the fixed cap changes sizing for **all** connection banners; the
overlay clamp is what bounds it, so verify very long errors scroll rather than
push the banner off the top of the transcript.
- No backend / shared DTO changes; `UNSUPPORTED` + `error=reason` already reach
the frontend.
- Reason codes live only in `RemoteDirectory.kt` today; the new bundle keys are
the first human-readable mapping. New reason codes fall back to `unknown`.
## Open Questions
- Exact user-facing wording for the five `session.connection.unsupported*`
strings (placeholder copy above).
+1 -1
View File
@@ -359,7 +359,7 @@
},
"packages/kilo-jetbrains": {
"name": "@kilocode/kilo-jetbrains",
"version": "7.4.21",
"version": "7.4.22",
},
"packages/kilo-memory": {
"name": "@kilocode/kilo-memory",
@@ -5,13 +5,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--no-configuration-cache --purge-old-log-directories -Pkilo.dev.log.level=debug -Pkilo.dev.log.chat.content=off -Pkilo.dev.log.chat.preview.max=160 -Pkilo.splitModeServerPort=0 -Pkilo.dev.storage.isolated=true" />
<option name="scriptParameters" value="--no-configuration-cache -Pkilo.dev.log.level=debug -Pkilo.dev.log.chat.content=off -Pkilo.dev.log.chat.preview.max=160 -Pkilo.splitModeServerPort=0 -Pkilo.dev.storage.isolated=true" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":runIdeBackend" />
<option value="--purge-old-log-directories" />
</list>
</option>
<option name="vmOptions" value="" />
@@ -25,4 +26,4 @@
<GradleCoverageDisabled>false</GradleCoverageDisabled>
<method v="2" />
</configuration>
</component>
</component>
@@ -6,13 +6,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--no-configuration-cache --purge-old-log-directories -Pkilo.dev.log.level=debug -Pkilo.dev.log.chat.content=off -Pkilo.dev.log.chat.preview.max=160 -Pkilo.splitModeServerPort=0 -Pkilo.dev.storage.isolated=true" />
<option name="scriptParameters" value="--no-configuration-cache -Pkilo.dev.log.level=debug -Pkilo.dev.log.chat.content=off -Pkilo.dev.log.chat.preview.max=160 -Pkilo.splitModeServerPort=0 -Pkilo.dev.storage.isolated=true" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":runIdeSplitMode" />
<option value="--purge-old-log-directories" />
</list>
</option>
<option name="vmOptions" value="" />
@@ -26,4 +27,4 @@
<GradleCoverageDisabled>false</GradleCoverageDisabled>
<method v="2" />
</configuration>
</component>
</component>
@@ -9,7 +9,6 @@ internal object RemoteDirectory {
private val WSL_LOCALHOST = "\\\\wsl.localhost\\"
fun detect(directory: String): String? {
forced()?.let { return it }
val dir = directory.trim()
if (dir.contains(DEVCONTAINER)) return "devcontainer_virtual_filesystem"
if (dir.startsWith(WSL, ignoreCase = true)) return "wsl_virtual_filesystem"
@@ -21,15 +20,4 @@ internal object RemoteDirectory {
"invalid_virtual_path"
}
}
/**
* Dev-only override so the unsupported notice can be reproduced without a real
* Dev Container / IJent path. Set `-Dkilo.dev.forceUnsupportedWorkspace=<reason>`
* (or `=true`) on a dev IDE run to force every workspace into the Unsupported state.
*/
private fun forced(): String? {
val flag = System.getProperty("kilo.dev.forceUnsupportedWorkspace")?.trim().orEmpty()
if (flag.isEmpty() || flag.equals("false", ignoreCase = true)) return null
return if (flag.equals("true", ignoreCase = true)) "devcontainer_virtual_filesystem" else flag
}
}
@@ -1,19 +1,11 @@
package ai.kilocode.backend.workspace
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class RemoteDirectoryTest {
private val flag = "kilo.dev.forceUnsupportedWorkspace"
@AfterTest
fun tearDown() {
System.clearProperty(flag)
}
@Test
fun `detects devcontainer virtual path`() {
val dir = "/${'$'}devcontainer.ij/abc@u~run~user~1001~podman~podman.sock/workspaces/project"
@@ -36,16 +28,4 @@ class RemoteDirectoryTest {
assertNull(RemoteDirectory.detect("/Users/dev/project"))
assertNull(RemoteDirectory.detect("/workspaces/project"))
}
@Test
fun `forced flag overrides any directory`() {
System.setProperty(flag, "true")
assertEquals("devcontainer_virtual_filesystem", RemoteDirectory.detect("/Users/dev/project"))
System.setProperty(flag, "custom_reason")
assertEquals("custom_reason", RemoteDirectory.detect("/Users/dev/project"))
System.setProperty(flag, "false")
assertNull(RemoteDirectory.detect("/Users/dev/project"))
}
}
-3
View File
@@ -276,9 +276,6 @@ tasks.withType<RunIdeTask> {
systemProperty("kilo.dev.log.chat.preview.max", preview)
systemProperty("kilo.dev.storage.isolated", isolated.get().toString())
systemProperty("kilo.dev.worktree.root", worktreeRoot.get())
providers.gradleProperty("kilo.dev.forceUnsupportedWorkspace").orNull?.let {
systemProperty("kilo.dev.forceUnsupportedWorkspace", it)
}
}
tasks.named<Delete>("clean") {
@@ -440,14 +440,17 @@ class SessionUi(
)
connection = ConnectionPanel(this, controller)
root.addOverlay(connection) { pane, child ->
val size = child.preferredSize
val point = SwingUtilities.convertPoint(prompt.parent ?: root.content, prompt.x, prompt.y, pane)
val gap = SessionUiStyle.View.contentGap()
val wide = (prompt.width - gap * 2).coerceAtLeast(0)
// Fix the banner width before measuring so its word-wrapped detail height is known.
child.setSize(wide, child.height)
val height = child.preferredSize.height.coerceAtMost((point.y - gap).coerceAtLeast(0))
java.awt.Rectangle(
point.x + gap,
point.y - size.height - gap,
(prompt.width - gap * 2).coerceAtLeast(0),
size.height,
point.y - height - gap,
wide,
height,
)
}
@@ -2378,6 +2378,14 @@ class SessionController(
)
}
if (workspace.status == KiloWorkspaceStatusDto.UNSUPPORTED) {
return SessionControllerEvent.ConnectionChanged.ShowError(
KiloBundle.message("session.connection.unsupported"),
unsupported(workspace.error, directory),
"workspace",
)
}
if (app.status == KiloAppStatusDto.READY && workspace.status == KiloWorkspaceStatusDto.READY && app.warnings.isNotEmpty()) {
return SessionControllerEvent.ConnectionChanged.ShowWarning(
summary(app.warnings.size),
@@ -2576,6 +2584,18 @@ private fun summary(count: Int): String {
return "$base ($count)"
}
private fun unsupported(reason: String?, directory: String): String {
val detail = when (reason) {
"devcontainer_virtual_filesystem" -> KiloBundle.message("session.connection.unsupported.devcontainer")
"wsl_virtual_filesystem" -> KiloBundle.message("session.connection.unsupported.wsl")
"invalid_virtual_path" -> KiloBundle.message("session.connection.unsupported.invalid")
else -> KiloBundle.message("session.connection.unsupported.unknown")
}
val path = KiloBundle.message("session.connection.unsupported.path", directory)
val options = KiloBundle.message("session.connection.unsupported.options")
return "$path\n\n$detail\n\n$options"
}
private const val KILO_PROVIDER = "kilo"
private const val KILO_AUTO_MODEL = "kilo-auto/free"
@@ -42,7 +42,6 @@ class ConnectionPanel(
companion object {
internal const val CLI_GROUP_ID = "Kilo.CliGroup"
private const val DETAILS_LINES = 10
private const val CHROME = 2
}
@@ -301,8 +300,14 @@ class ConnectionPanel(
}
private fun scrollHeight(): Int {
val rows = details.text.lineSequence().count().coerceIn(1, DETAILS_LINES)
return details.getFontMetrics(details.font).height * rows + scrollChrome()
val inner = (width - scroll.insets.left - scroll.insets.right).coerceAtLeast(1)
// Measure the word-wrapped height at the available width so a single long line that wraps
// onto several visual rows still contributes its full height instead of being clipped.
details.setSize(inner, Short.MAX_VALUE.toInt())
val wrapped = details.preferredSize.height
val rows = details.text.lineSequence().count().coerceAtLeast(1)
val minimum = details.getFontMetrics(details.font).height * rows
return maxOf(wrapped, minimum) + scrollChrome()
}
private fun scrollChrome() = scroll.insets.top + scroll.insets.bottom + JBUI.scale(CHROME)
@@ -341,7 +346,4 @@ class ConnectionPanel(
internal fun retryFocusable() = retry.isFocusable
internal fun hasSeparator() = border != null
internal fun maxExpandedHeight() =
header.preferredSize.height + details.getFontMetrics(details.font).height * DETAILS_LINES + scrollChrome()
}
@@ -14,6 +14,13 @@ session.connection.error.app=Connection failed
session.connection.error.workspace=Workspace loading failed
session.connection.error.unknown=Unknown error
session.connection.retry=Try again
session.connection.unsupported=Workspace not supported
session.connection.unsupported.devcontainer=Kilo runs on your host machine, so it can't reach the files inside this Dev Container.
session.connection.unsupported.invalid=Kilo can't resolve this workspace path on your local filesystem.
session.connection.unsupported.unknown=Kilo runs on your host machine, so it can't reach this workspace's files.
session.connection.unsupported.path=Workspace path: {0}
session.connection.unsupported.wsl=Kilo runs on your host machine, so it can't reach the files inside WSL.
session.connection.unsupported.options=Option 1: Open the project in the container or WSL with JetBrains Gateway so Kilo runs next to your code.\nOption 2: Open the project directly from your local filesystem so Kilo can reach the files.
session.connection.warning.config=Configuration warnings
notification.group.kilo=Kilo Code
@@ -7,6 +7,13 @@ session.connection.error.app=فشل الاتصال
session.connection.error.workspace=فشل تحميل مساحة العمل
session.connection.error.unknown=خطأ غير معروف
session.connection.retry=إعادة المحاولة
session.connection.unsupported=مساحة العمل غير مدعومة
session.connection.unsupported.devcontainer=يعمل Kilo على جهازك المضيف، لذا لا يمكنه الوصول إلى الملفات داخل Dev Container هذا.
session.connection.unsupported.invalid=لا يستطيع Kilo تحليل مسار مساحة العمل هذا على نظام الملفات المحلي لديك.
session.connection.unsupported.unknown=يعمل Kilo على جهازك المضيف، لذا لا يمكنه الوصول إلى ملفات مساحة العمل هذه.
session.connection.unsupported.path=مسار مساحة العمل: {0}
session.connection.unsupported.wsl=يعمل Kilo على جهازك المضيف، لذا لا يمكنه الوصول إلى الملفات داخل WSL.
session.connection.unsupported.options=الخيار 1: افتح المشروع داخل الحاوية أو WSL باستخدام JetBrains Gateway حتى يعمل Kilo بجوار التعليمات البرمجية الخاصة بك.\nالخيار 2: افتح المشروع مباشرةً من نظام الملفات المحلي لديك حتى يتمكن Kilo من الوصول إلى الملفات.
session.connection.warning.config=تحذيرات التكوين
session.empty.welcome=Kilo Code هو مساعد برمجة بالذكاء الاصطناعي. اطلب منه بناء ميزات أو إصلاح أخطاء أو شرح قاعدة الكود.
@@ -7,6 +7,13 @@ session.connection.error.app=Greška pri spajanju
session.connection.error.workspace=Greška pri učitavanju radnog prostora
session.connection.error.unknown=Nepoznata greška
session.connection.retry=Pokušaj ponovo
session.connection.unsupported=Radni prostor nije podržan
session.connection.unsupported.devcontainer=Kilo se izvršava na vašem host računaru, pa ne može pristupiti datotekama unutar ovog Dev Container-a.
session.connection.unsupported.invalid=Kilo ne može razriješiti ovu putanju radnog prostora na vašem lokalnom datotečnom sistemu.
session.connection.unsupported.unknown=Kilo se izvršava na vašem host računaru, pa ne može pristupiti datotekama ovog radnog prostora.
session.connection.unsupported.path=Putanja radnog prostora: {0}
session.connection.unsupported.wsl=Kilo se izvršava na vašem host računaru, pa ne može pristupiti datotekama unutar WSL-a.
session.connection.unsupported.options=Opcija 1: Otvorite projekat u kontejneru ili WSL-u pomoću JetBrains Gateway-a kako bi se Kilo izvršavao uz vaš kod.\nOpcija 2: Otvorite projekat direktno sa vašeg lokalnog datotečnog sistema kako bi Kilo mogao pristupiti datotekama.
session.connection.warning.config=Upozorenja konfiguracije
session.empty.welcome=Kilo Code je AI asistent za kodiranje. Zatražite od njega da gradi funkcije, ispravlja greške ili objašnjava vašu bazu koda.
@@ -7,6 +7,13 @@ session.connection.error.app=Forbindelsesfejl
session.connection.error.workspace=Fejl ved indlæsning af arbejdsområde
session.connection.error.unknown=Ukendt fejl
session.connection.retry=Prøv igen
session.connection.unsupported=Arbejdsområde understøttes ikke
session.connection.unsupported.devcontainer=Kilo kører på din værtsmaskine og kan derfor ikke nå filerne inde i denne Dev Container.
session.connection.unsupported.invalid=Kilo kan ikke fortolke denne arbejdsområdesti på dit lokale filsystem.
session.connection.unsupported.unknown=Kilo kører på din værtsmaskine og kan derfor ikke nå filerne i dette arbejdsområde.
session.connection.unsupported.path=Arbejdsområdesti: {0}
session.connection.unsupported.wsl=Kilo kører på din værtsmaskine og kan derfor ikke nå filerne inde i WSL.
session.connection.unsupported.options=Mulighed 1: Åbn projektet i containeren eller WSL med JetBrains Gateway, så Kilo kører ved siden af din kode.\nMulighed 2: Åbn projektet direkte fra dit lokale filsystem, så Kilo kan få adgang til filerne.
session.connection.warning.config=Konfigurationsadvarsler
session.empty.welcome=Kilo Code er en AI-kodningsassistent. Bed den om at bygge funktioner, rette fejl eller forklare din kodebase.
@@ -7,6 +7,13 @@ session.connection.error.app=Verbindung fehlgeschlagen
session.connection.error.workspace=Workspace-Laden fehlgeschlagen
session.connection.error.unknown=Unbekannter Fehler
session.connection.retry=Erneut versuchen
session.connection.unsupported=Workspace nicht unterstützt
session.connection.unsupported.devcontainer=Kilo läuft auf Ihrem Host-Rechner und kann daher nicht auf die Dateien in diesem Dev Container zugreifen.
session.connection.unsupported.invalid=Kilo kann diesen Workspace-Pfad auf Ihrem lokalen Dateisystem nicht auflösen.
session.connection.unsupported.unknown=Kilo läuft auf Ihrem Host-Rechner und kann daher nicht auf die Dateien dieses Workspaces zugreifen.
session.connection.unsupported.path=Workspace-Pfad: {0}
session.connection.unsupported.wsl=Kilo läuft auf Ihrem Host-Rechner und kann daher nicht auf die Dateien in WSL zugreifen.
session.connection.unsupported.options=Option 1: Öffnen Sie das Projekt im Container oder in WSL mit JetBrains Gateway, damit Kilo direkt neben Ihrem Code läuft.\nOption 2: Öffnen Sie das Projekt direkt aus Ihrem lokalen Dateisystem, damit Kilo auf die Dateien zugreifen kann.
session.connection.warning.config=Konfigurationswarnungen
session.empty.welcome=Kilo Code ist ein KI-Coding-Assistent. Bitten Sie ihn, Funktionen zu erstellen, Fehler zu beheben oder Ihre Codebasis zu erklären.
@@ -7,6 +7,13 @@ session.connection.error.app=Error de conexión
session.connection.error.workspace=Error al cargar el espacio de trabajo
session.connection.error.unknown=Error desconocido
session.connection.retry=Reintentar
session.connection.unsupported=Espacio de trabajo no compatible
session.connection.unsupported.devcontainer=Kilo se ejecuta en tu máquina host, por lo que no puede acceder a los archivos dentro de este Dev Container.
session.connection.unsupported.invalid=Kilo no puede resolver esta ruta del espacio de trabajo en tu sistema de archivos local.
session.connection.unsupported.unknown=Kilo se ejecuta en tu máquina host, por lo que no puede acceder a los archivos de este espacio de trabajo.
session.connection.unsupported.path=Ruta del espacio de trabajo: {0}
session.connection.unsupported.wsl=Kilo se ejecuta en tu máquina host, por lo que no puede acceder a los archivos dentro de WSL.
session.connection.unsupported.options=Opción 1: Abre el proyecto en el contenedor o WSL con JetBrains Gateway para que Kilo se ejecute junto a tu código.\nOpción 2: Abre el proyecto directamente desde tu sistema de archivos local para que Kilo pueda acceder a los archivos.
session.connection.warning.config=Advertencias de configuración
session.empty.welcome=Kilo Code es un asistente de codificación con IA. Pida que construya funciones, corrija errores o explique su base de código.
@@ -7,6 +7,13 @@ session.connection.error.app=Échec de la connexion
session.connection.error.workspace=Échec du chargement de l'espace de travail
session.connection.error.unknown=Erreur inconnue
session.connection.retry=Réessayer
session.connection.unsupported=Espace de travail non pris en charge
session.connection.unsupported.devcontainer=Kilo s'exécute sur votre machine hôte et ne peut donc pas accéder aux fichiers de ce Dev Container.
session.connection.unsupported.invalid=Kilo ne parvient pas à résoudre ce chemin d'espace de travail sur votre système de fichiers local.
session.connection.unsupported.unknown=Kilo s'exécute sur votre machine hôte et ne peut donc pas accéder aux fichiers de cet espace de travail.
session.connection.unsupported.path=Chemin de l'espace de travail : {0}
session.connection.unsupported.wsl=Kilo s'exécute sur votre machine hôte et ne peut donc pas accéder aux fichiers dans WSL.
session.connection.unsupported.options=Option 1 : ouvrez le projet dans le conteneur ou WSL avec JetBrains Gateway afin que Kilo s'exécute à côté de votre code.\nOption 2 : ouvrez le projet directement depuis votre système de fichiers local afin que Kilo puisse accéder aux fichiers.
session.connection.warning.config=Avertissements de configuration
session.empty.welcome=Kilo Code est un assistant de codage IA. Demandez-lui de créer des fonctionnalités, corriger des bugs ou expliquer votre base de code.
@@ -7,6 +7,13 @@ session.connection.error.app=接続に失敗しました
session.connection.error.workspace=ワークスペースの読み込みに失敗しました
session.connection.error.unknown=不明なエラー
session.connection.retry=再試行
session.connection.unsupported=ワークスペースはサポートされていません
session.connection.unsupported.devcontainer=Kilo はホストマシン上で実行されるため、この Dev Container 内のファイルにアクセスできません。
session.connection.unsupported.invalid=Kilo はこのワークスペースのパスをローカルファイルシステム上で解決できません。
session.connection.unsupported.unknown=Kilo はホストマシン上で実行されるため、このワークスペースのファイルにアクセスできません。
session.connection.unsupported.path=ワークスペースのパス: {0}
session.connection.unsupported.wsl=Kilo はホストマシン上で実行されるため、WSL 内のファイルにアクセスできません。
session.connection.unsupported.options=オプション1: JetBrains Gateway を使ってコンテナまたは WSL 内でプロジェクトを開き、Kilo がコードのそばで実行されるようにします。\nオプション2: プロジェクトをローカルファイルシステムから直接開き、Kilo がファイルにアクセスできるようにします。
session.connection.warning.config=設定の警告
session.empty.welcome=Kilo CodeはAIコーディングアシスタントです。機能の作成、バグの修正、またはコードベースの説明を依頼できます。
@@ -7,6 +7,13 @@ session.connection.error.app=연결 실패
session.connection.error.workspace=워크스페이스 로딩 실패
session.connection.error.unknown=알 수 없는 오류
session.connection.retry=다시 시도
session.connection.unsupported=지원되지 않는 워크스페이스
session.connection.unsupported.devcontainer=Kilo는 호스트 머신에서 실행되므로 이 Dev Container 안의 파일에 접근할 수 없습니다.
session.connection.unsupported.invalid=Kilo가 로컬 파일 시스템에서 이 워크스페이스 경로를 확인할 수 없습니다.
session.connection.unsupported.unknown=Kilo는 호스트 머신에서 실행되므로 이 워크스페이스의 파일에 접근할 수 없습니다.
session.connection.unsupported.path=워크스페이스 경로: {0}
session.connection.unsupported.wsl=Kilo는 호스트 머신에서 실행되므로 WSL 안의 파일에 접근할 수 없습니다.
session.connection.unsupported.options=옵션 1: JetBrains Gateway로 컨테이너 또는 WSL에서 프로젝트를 열어 Kilo가 코드 옆에서 실행되도록 합니다.\n옵션 2: 로컬 파일 시스템에서 프로젝트를 직접 열어 Kilo가 파일에 접근할 수 있도록 합니다.
session.connection.warning.config=구성 경고
session.empty.welcome=Kilo Code는 AI 코딩 어시스턴트입니다. 기능 구축, 버그 수정, 코드베이스 설명을 요청하세요.
@@ -7,6 +7,13 @@ session.connection.error.app=Verbinding mislukt
session.connection.error.workspace=Werkruimte laden mislukt
session.connection.error.unknown=Onbekende fout
session.connection.retry=Opnieuw proberen
session.connection.unsupported=Werkruimte niet ondersteund
session.connection.unsupported.devcontainer=Kilo draait op je hostmachine en heeft daardoor geen toegang tot de bestanden in deze Dev Container.
session.connection.unsupported.invalid=Kilo kan dit werkruimtepad niet omzetten op je lokale bestandssysteem.
session.connection.unsupported.unknown=Kilo draait op je hostmachine en heeft daardoor geen toegang tot de bestanden van deze werkruimte.
session.connection.unsupported.path=Werkruimtepad: {0}
session.connection.unsupported.wsl=Kilo draait op je hostmachine en heeft daardoor geen toegang tot de bestanden in WSL.
session.connection.unsupported.options=Optie 1: Open het project in de container of WSL met JetBrains Gateway zodat Kilo naast je code draait.\nOptie 2: Open het project rechtstreeks vanuit je lokale bestandssysteem zodat Kilo bij de bestanden kan.
session.connection.warning.config=Configuratiewaarschuwingen
session.empty.welcome=Kilo Code is een AI-codeerassistent. Vraag het om functies te bouwen, bugs te repareren of uw codebase uit te leggen.
@@ -7,6 +7,13 @@ session.connection.error.app=Tilkoblingsfeil
session.connection.error.workspace=Feil ved lasting av arbeidsområde
session.connection.error.unknown=Ukjent feil
session.connection.retry=Prøv igjen
session.connection.unsupported=Arbeidsområde støttes ikke
session.connection.unsupported.devcontainer=Kilo kjører på vertsmaskinen din og kan derfor ikke nå filene inne i denne Dev Container.
session.connection.unsupported.invalid=Kilo kan ikke tolke denne arbeidsområdestien på det lokale filsystemet.
session.connection.unsupported.unknown=Kilo kjører på vertsmaskinen din og kan derfor ikke nå filene i dette arbeidsområdet.
session.connection.unsupported.path=Arbeidsområdesti: {0}
session.connection.unsupported.wsl=Kilo kjører på vertsmaskinen din og kan derfor ikke nå filene inne i WSL.
session.connection.unsupported.options=Alternativ 1: Åpne prosjektet i containeren eller WSL med JetBrains Gateway, slik at Kilo kjører ved siden av koden din.\nAlternativ 2: Åpne prosjektet direkte fra det lokale filsystemet, slik at Kilo får tilgang til filene.
session.connection.warning.config=Konfigurasjonsadvarsler
session.empty.welcome=Kilo Code er en AI-kodingsassistent. Be den om å bygge funksjoner, fikse feil eller forklare kodebasen din.
@@ -7,6 +7,13 @@ session.connection.error.app=Błąd połączenia
session.connection.error.workspace=Błąd ładowania obszaru roboczego
session.connection.error.unknown=Nieznany błąd
session.connection.retry=Spróbuj ponownie
session.connection.unsupported=Obszar roboczy nieobsługiwany
session.connection.unsupported.devcontainer=Kilo działa na Twoim komputerze hosta, więc nie ma dostępu do plików w tym Dev Container.
session.connection.unsupported.invalid=Kilo nie może rozpoznać tej ścieżki obszaru roboczego w lokalnym systemie plików.
session.connection.unsupported.unknown=Kilo działa na Twoim komputerze hosta, więc nie ma dostępu do plików tego obszaru roboczego.
session.connection.unsupported.path=Ścieżka obszaru roboczego: {0}
session.connection.unsupported.wsl=Kilo działa na Twoim komputerze hosta, więc nie ma dostępu do plików w WSL.
session.connection.unsupported.options=Opcja 1: Otwórz projekt w kontenerze lub WSL za pomocą JetBrains Gateway, aby Kilo działało obok Twojego kodu.\nOpcja 2: Otwórz projekt bezpośrednio z lokalnego systemu plików, aby Kilo miało dostęp do plików.
session.connection.warning.config=Ostrzeżenia konfiguracji
session.empty.welcome=Kilo Code to asystent kodowania AI. Poproś go o tworzenie funkcji, naprawianie błędów lub wyjaśnianie bazy kodu.
@@ -7,6 +7,13 @@ session.connection.error.app=Falha na conexão
session.connection.error.workspace=Falha ao carregar o espaço de trabalho
session.connection.error.unknown=Erro desconhecido
session.connection.retry=Tentar novamente
session.connection.unsupported=Espaço de trabalho sem suporte
session.connection.unsupported.devcontainer=O Kilo é executado na sua máquina host, então não consegue acessar os arquivos dentro deste Dev Container.
session.connection.unsupported.invalid=O Kilo não consegue resolver este caminho do espaço de trabalho no seu sistema de arquivos local.
session.connection.unsupported.unknown=O Kilo é executado na sua máquina host, então não consegue acessar os arquivos deste espaço de trabalho.
session.connection.unsupported.path=Caminho do espaço de trabalho: {0}
session.connection.unsupported.wsl=O Kilo é executado na sua máquina host, então não consegue acessar os arquivos dentro do WSL.
session.connection.unsupported.options=Opção 1: Abra o projeto no contêiner ou no WSL com o JetBrains Gateway para que o Kilo seja executado junto ao seu código.\nOpção 2: Abra o projeto diretamente do seu sistema de arquivos local para que o Kilo possa acessar os arquivos.
session.connection.warning.config=Avisos de configuração
session.empty.welcome=Kilo Code é um assistente de codificação com IA. Peça que construa funcionalidades, corrija bugs ou explique sua base de código.
@@ -7,6 +7,13 @@ session.connection.error.app=Ошибка подключения
session.connection.error.workspace=Ошибка загрузки рабочей области
session.connection.error.unknown=Неизвестная ошибка
session.connection.retry=Повторить
session.connection.unsupported=Рабочая область не поддерживается
session.connection.unsupported.devcontainer=Kilo работает на вашей хост-машине и поэтому не может получить доступ к файлам внутри этого Dev Container.
session.connection.unsupported.invalid=Kilo не может разрешить этот путь рабочей области в локальной файловой системе.
session.connection.unsupported.unknown=Kilo работает на вашей хост-машине и поэтому не может получить доступ к файлам этой рабочей области.
session.connection.unsupported.path=Путь рабочей области: {0}
session.connection.unsupported.wsl=Kilo работает на вашей хост-машине и поэтому не может получить доступ к файлам внутри WSL.
session.connection.unsupported.options=Вариант 1: откройте проект в контейнере или WSL с помощью JetBrains Gateway, чтобы Kilo работал рядом с вашим кодом.\nВариант 2: откройте проект напрямую из локальной файловой системы, чтобы Kilo мог получить доступ к файлам.
session.connection.warning.config=Предупреждения конфигурации
session.empty.welcome=Kilo Code — это AI-ассистент по программированию. Попросите его разработать функции, исправить ошибки или объяснить кодовую базу.
@@ -7,6 +7,13 @@ session.connection.error.app=การเชื่อมต่อล้มเห
session.connection.error.workspace=โหลดพื้นที่ทำงานล้มเหลว
session.connection.error.unknown=ข้อผิดพลาดที่ไม่ทราบ
session.connection.retry=ลองอีกครั้ง
session.connection.unsupported=ไม่รองรับพื้นที่ทำงาน
session.connection.unsupported.devcontainer=Kilo ทำงานบนเครื่องโฮสต์ของคุณ จึงไม่สามารถเข้าถึงไฟล์ภายใน Dev Container นี้ได้
session.connection.unsupported.invalid=Kilo ไม่สามารถแปลงเส้นทางพื้นที่ทำงานนี้บนระบบไฟล์ในเครื่องของคุณได้
session.connection.unsupported.unknown=Kilo ทำงานบนเครื่องโฮสต์ของคุณ จึงไม่สามารถเข้าถึงไฟล์ของพื้นที่ทำงานนี้ได้
session.connection.unsupported.path=เส้นทางพื้นที่ทำงาน: {0}
session.connection.unsupported.wsl=Kilo ทำงานบนเครื่องโฮสต์ของคุณ จึงไม่สามารถเข้าถึงไฟล์ภายใน WSL ได้
session.connection.unsupported.options=ตัวเลือกที่ 1: เปิดโปรเจกต์ในคอนเทนเนอร์หรือ WSL ด้วย JetBrains Gateway เพื่อให้ Kilo ทำงานข้างโค้ดของคุณ\nตัวเลือกที่ 2: เปิดโปรเจกต์โดยตรงจากระบบไฟล์ในเครื่องของคุณ เพื่อให้ Kilo เข้าถึงไฟล์ได้
session.connection.warning.config=คำเตือนการกำหนดค่า
session.empty.welcome=Kilo Code คือผู้ช่วยเขียนโค้ด AI ขอให้สร้างฟีเจอร์แก้สันข้อผิดพลาด หรืออธิบายโค้ดเบสของคุณ
@@ -7,6 +7,13 @@ session.connection.error.app=Bağlantı hatası
session.connection.error.workspace=Çalışma alanı yüklenemedi
session.connection.error.unknown=Bilinmeyen hata
session.connection.retry=Tekrar dene
session.connection.unsupported=Çalışma alanı desteklenmiyor
session.connection.unsupported.devcontainer=Kilo, ana makinenizde çalışır ve bu nedenle bu Dev Container içindeki dosyalara erişemez.
session.connection.unsupported.invalid=Kilo, bu çalışma alanı yolunu yerel dosya sisteminizde çözümleyemiyor.
session.connection.unsupported.unknown=Kilo, ana makinenizde çalışır ve bu nedenle bu çalışma alanının dosyalarına erişemez.
session.connection.unsupported.path=Çalışma alanı yolu: {0}
session.connection.unsupported.wsl=Kilo, ana makinenizde çalışır ve bu nedenle WSL içindeki dosyalara erişemez.
session.connection.unsupported.options=Seçenek 1: Projeyi JetBrains Gateway ile container veya WSL içinde açın, böylece Kilo kodunuzun yanında çalışır.\nSeçenek 2: Projeyi doğrudan yerel dosya sisteminizden açın, böylece Kilo dosyalara erişebilir.
session.connection.warning.config=Yapılandırma uyarıları
session.empty.welcome=Kilo Code, bir yapay zeka kodlama asistanıdır. Özellik oluşturmasını, hata düzeltirlmesi veya kod tabanınızı açıklamasını isteyin.
@@ -7,6 +7,13 @@ session.connection.error.app=Помилка з'єднання
session.connection.error.workspace=Помилка завантаження робочого простору
session.connection.error.unknown=Невідома помилка
session.connection.retry=Спробувати знову
session.connection.unsupported=Робочий простір не підтримується
session.connection.unsupported.devcontainer=Kilo працює на вашій хост-машині, тому не може отримати доступ до файлів усередині цього Dev Container.
session.connection.unsupported.invalid=Kilo не може розв'язати цей шлях робочого простору у локальній файловій системі.
session.connection.unsupported.unknown=Kilo працює на вашій хост-машині, тому не може отримати доступ до файлів цього робочого простору.
session.connection.unsupported.path=Шлях робочого простору: {0}
session.connection.unsupported.wsl=Kilo працює на вашій хост-машині, тому не може отримати доступ до файлів усередині WSL.
session.connection.unsupported.options=Варіант 1: відкрийте проєкт у контейнері або WSL за допомогою JetBrains Gateway, щоб Kilo працював поруч із вашим кодом.\nВаріант 2: відкрийте проєкт безпосередньо з локальної файлової системи, щоб Kilo міг отримати доступ до файлів.
session.connection.warning.config=Попередження конфігурації
session.empty.welcome=Kilo Code — це AI-асистент для програмування. Попросіть його створити функції, виправити помилки або пояснити ваш код.
@@ -7,6 +7,13 @@ session.connection.error.app=连接失败
session.connection.error.workspace=工作区加载失败
session.connection.error.unknown=未知错误
session.connection.retry=重试
session.connection.unsupported=不支持的工作区
session.connection.unsupported.devcontainer=Kilo 在你的宿主机上运行,因此无法访问此 Dev Container 内的文件。
session.connection.unsupported.invalid=Kilo 无法在本地文件系统上解析此工作区路径。
session.connection.unsupported.unknown=Kilo 在你的宿主机上运行,因此无法访问此工作区的文件。
session.connection.unsupported.path=工作区路径:{0}
session.connection.unsupported.wsl=Kilo 在你的宿主机上运行,因此无法访问 WSL 内的文件。
session.connection.unsupported.options=选项 1:使用 JetBrains Gateway 在容器或 WSL 中打开项目,让 Kilo 与你的代码一起运行。\n选项 2:直接从本地文件系统打开项目,让 Kilo 能够访问这些文件。
session.connection.warning.config=配置警告
session.empty.welcome=Kilo Code 是一个 AI 编程助手。可请它构建功能、修复错误或解释您的代码库。
@@ -7,6 +7,13 @@ session.connection.error.app=連線失敗
session.connection.error.workspace=工作區載入失敗
session.connection.error.unknown=未知錯誤
session.connection.retry=重試
session.connection.unsupported=不支援的工作區
session.connection.unsupported.devcontainer=Kilo 在你的主機上執行,因此無法存取此 Dev Container 內的檔案。
session.connection.unsupported.invalid=Kilo 無法在本機檔案系統上解析此工作區路徑。
session.connection.unsupported.unknown=Kilo 在你的主機上執行,因此無法存取此工作區的檔案。
session.connection.unsupported.path=工作區路徑:{0}
session.connection.unsupported.wsl=Kilo 在你的主機上執行,因此無法存取 WSL 內的檔案。
session.connection.unsupported.options=選項 1:使用 JetBrains Gateway 在容器或 WSL 中開啟專案,讓 Kilo 與你的程式碼一起執行。\n選項 2:直接從本機檔案系統開啟專案,讓 Kilo 能夠存取這些檔案。
session.connection.warning.config=設定警告
session.empty.welcome=Kilo Code 是 AI 程式輔助。可請它建置功能、修復錯誤或解釋您的程式程式庫。
@@ -275,6 +275,29 @@ class SessionUiLayoutTest : SessionUiTestBase() {
assertEquals(promptPoint(root, prompt).y - SessionUiStyle.View.contentGap(), connection.y + connection.height)
}
fun `test expanded connection panel is capped to transcript height`() {
ui.setSize(800, 260)
layout()
val root = find<SessionRootPanel>(ui)
val connection = find<ConnectionPanel>(ui)
val prompt = find<PromptPanel>(ui)
connection.onEvent(SessionControllerEvent.ConnectionChanged.ShowError(
"CLI startup failed",
lines(60),
))
layout()
connection.clickSummary()
layout()
val pane = connection.components.filterIsInstance<JBScrollPane>().single()
pane.doLayout()
assertTrue(connection.detailsVisible())
assertEquals(0, connection.y)
assertEquals(promptPoint(root, prompt).y - SessionUiStyle.View.contentGap(), connection.y + connection.height)
assertTrue(pane.viewport.extentSize.height < pane.viewport.view.preferredSize.height)
}
fun `test connection panel is unaffected by active question view`() {
ui = newUi(id = "ses_test")
settle()
@@ -775,6 +798,8 @@ class SessionUiLayoutTest : SessionUiTestBase() {
private fun promptPoint(root: SessionRootPanel, prompt: PromptPanel) =
SwingUtilities.convertPoint(prompt.parent, prompt.x, prompt.y, root.overlay)
private fun lines(count: Int) = (1..count).joinToString("\n") { "line $it" }
private class Row(override val sessionViewKind: SessionView.Kind) : JPanel(), SessionView {
override fun getPreferredSize() = Dimension(100, 10)
}
@@ -138,6 +138,61 @@ class ConnectionDelayTest : SessionControllerTestBase() {
assertEquals("providers: bad provider json", event.detail)
}
fun `test persistent unsupported workspace shows standard workspace error`() {
appRpc.state.value = KiloAppStateDto(KiloAppStatusDto.READY)
projectRpc.state.value = workspaceReady()
val m = controller(displayMs = 50)
val events = collect(m)
flush()
events.clear()
projectRpc.state.value = KiloWorkspaceStateDto(
status = KiloWorkspaceStatusDto.UNSUPPORTED,
error = "wsl_virtual_filesystem",
)
pause(20)
assertFalse(events.any { it is SessionControllerEvent.ConnectionChanged.ShowError })
pause(80)
val event = events.filterIsInstance<SessionControllerEvent.ConnectionChanged.ShowError>().single()
assertEquals("Workspace not supported", event.summary)
assertEquals(
"Workspace path: /test\n\n" +
"Kilo runs on your host machine, so it can't reach the files inside WSL.\n\n" +
"Option 1: Open the project in the container or WSL with JetBrains Gateway so Kilo runs next to your code.\n" +
"Option 2: Open the project directly from your local filesystem so Kilo can reach the files.",
event.detail,
)
assertEquals("workspace", event.source)
assertFalse(events.any { it is SessionControllerEvent.ConnectionChanged.ShowConnecting })
}
fun `test unsupported invalid path includes the workspace path`() {
appRpc.state.value = KiloAppStateDto(KiloAppStatusDto.READY)
projectRpc.state.value = workspaceReady()
val m = controller(displayMs = 50)
val events = collect(m)
flush()
events.clear()
projectRpc.state.value = KiloWorkspaceStateDto(
status = KiloWorkspaceStatusDto.UNSUPPORTED,
error = "invalid_virtual_path",
)
pause(80)
val event = events.filterIsInstance<SessionControllerEvent.ConnectionChanged.ShowError>().single()
assertEquals("Workspace not supported", event.summary)
assertEquals(
"Workspace path: /test\n\n" +
"Kilo can't resolve this workspace path on your local filesystem.\n\n" +
"Option 1: Open the project in the container or WSL with JetBrains Gateway so Kilo runs next to your code.\n" +
"Option 2: Open the project directly from your local filesystem so Kilo can reach the files.",
event.detail,
)
}
fun `test ready hides visible delayed connection banner immediately`() {
appRpc.state.value = KiloAppStateDto(KiloAppStatusDto.READY)
projectRpc.state.value = workspaceReady()
@@ -137,16 +137,41 @@ class ConnectionPanelTest : SessionControllerTestBase() {
assertTrue(panel.detailsVisible())
}
fun `test expanded details height is capped at ten lines`() {
fun `test expanded details preferred height fits full text`() {
edt {
panel.onEvent(SessionControllerEvent.ConnectionChanged.ShowError("CLI startup failed", lines(30)))
panel.onEvent(SessionControllerEvent.ConnectionChanged.ShowError("CLI startup failed", lines(10)))
panel.size = Dimension(480, 1000)
}
edt { panel.clickSummary() }
val ten = panel.preferredSize.height
edt {
panel.onEvent(SessionControllerEvent.ConnectionChanged.ShowError("CLI startup failed", lines(30)))
panel.clickSummary()
}
assertTrue(panel.detailsVisible())
assertTrue(panel.preferredSize.height <= panel.maxExpandedHeight())
assertTrue(panel.preferredSize.height > ten)
}
fun `test expanded details grow for a wrapped single line`() {
val sentence = (1..40).joinToString(" ") { "word$it" }
edt {
panel.onEvent(SessionControllerEvent.ConnectionChanged.ShowError("CLI startup failed", sentence))
panel.size = Dimension(240, 1000)
panel.clickSummary()
}
val fontHeight = fontHeight()
assertTrue(panel.detailsVisible())
// A single logical line that wraps must contribute more than one visual row of height.
assertTrue(panel.preferredSize.height > fontHeight * 2)
}
private fun fontHeight(): Int {
val details = panel.components.filterIsInstance<JBScrollPane>().single().viewport.view
return details.getFontMetrics(details.font).height
}
fun `test raw app and workspace events do not render panel`() {