diff --git a/.changeset/chilly-dancers-build.md b/.changeset/chilly-dancers-build.md new file mode 100644 index 00000000000..3f8b568bc5d --- /dev/null +++ b/.changeset/chilly-dancers-build.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Add a JetBrains tool window action for starting new sessions and let recent sessions reopen in the side panel without duplicate views. diff --git a/.changeset/fuzzy-berries-press.md b/.changeset/fuzzy-berries-press.md new file mode 100644 index 00000000000..0c48bad5166 --- /dev/null +++ b/.changeset/fuzzy-berries-press.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Show JetBrains session connection status inline above the prompt, cap expanded connection details, and keep empty sessions in the shared chat scroll area. diff --git a/.changeset/green-planes-reply.md b/.changeset/green-planes-reply.md new file mode 100644 index 00000000000..f9b3517d9c4 --- /dev/null +++ b/.changeset/green-planes-reply.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Show detailed JetBrains session connection and configuration issues in the connection panel, and make retry recover app-side problems more reliably. diff --git a/.changeset/permission-apply-patch-diffs.md b/.changeset/permission-apply-patch-diffs.md deleted file mode 100644 index 2d115ae7c20..00000000000 --- a/.changeset/permission-apply-patch-diffs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Show apply_patch diffs in the permission prompt before approval. diff --git a/.changeset/slow-terms-dance.md b/.changeset/slow-terms-dance.md new file mode 100644 index 00000000000..777d5bec8e7 --- /dev/null +++ b/.changeset/slow-terms-dance.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Keep the JetBrains session connection status above the prompt without shifting the chat layout. diff --git a/.changeset/soft-garlic-care.md b/.changeset/soft-garlic-care.md new file mode 100644 index 00000000000..ee6f55a6d61 --- /dev/null +++ b/.changeset/soft-garlic-care.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Refine JetBrains session loading UI and settings actions. diff --git a/.changeset/tidy-otters-wait.md b/.changeset/tidy-otters-wait.md new file mode 100644 index 00000000000..151fcfe79af --- /dev/null +++ b/.changeset/tidy-otters-wait.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Avoid flickering transient loading and connection error states in JetBrains sessions. diff --git a/.editorconfig b/.editorconfig index aada95f26a8..35ebc0b32db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,8 @@ end_of_line = lf indent_style = space indent_size = 2 max_line_length = 80 + +[*.{kt,kts}] +indent_size = 4 +tab_width = 4 +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL diff --git a/.kilo/skills/jetbrains-ui-style/SKILL.md b/.kilo/skills/jetbrains-ui-style/SKILL.md new file mode 100644 index 00000000000..5536a2c4459 --- /dev/null +++ b/.kilo/skills/jetbrains-ui-style/SKILL.md @@ -0,0 +1,888 @@ +--- +name: jetbrains-ui-style +description: Use when creating, modifying, or reviewing Kotlin/Swing UI code for the Kilo JetBrains plugin. Applies to dialogs, settings pages, tool windows, forms, panels, component layout, sizing, spacing, colors, borders, icons, lists, trees, popups, notifications, and IntelliJ platform UI components. +--- + +# JetBrains UI Style + +Use this skill whenever creating, modifying, or reviewing UI code in `packages/kilo-jetbrains`. + +## Scope + +This skill covers Kotlin/Swing UI code for the Kilo JetBrains plugin, including dialogs, settings pages, tool windows, forms, panels, component layout, sizing, spacing, colors, borders, icons, lists, trees, popups, notifications, and IntelliJ Platform UI components. + +This plugin is a split-mode JetBrains plugin. UI code belongs in `frontend` unless there is a specific split-mode reason to place it elsewhere. + +## Primary Rules + +- Prefer Kotlin UI DSL v2 whenever the UI is a dialog, settings page, form, options panel, or structured component layout. +- Use manual Swing only for custom rendering, transcript/chat surfaces, highly dynamic panels, virtualized lists, custom painting, or layouts the DSL cannot express cleanly. +- Keep generated UI code minimal. +- Do not set default Swing properties explicitly. For example, avoid `isOpaque = false` unless the component default differs or there is a documented rendering reason. +- Avoid hardcoded dimensions. Prefer layout semantics, component defaults, DSL sizing helpers, and `JBUI` utilities. +- Do not add decorative helper functions, wrappers, or defensive UI code unless they materially improve clarity or correctness. +- Prefer IntelliJ platform components and theme-aware APIs. +- Do not use hardcoded runtime colors in generated UI examples or implementation code. +- Derive UI colors from semantic IntelliJ theme APIs such as `UIUtil`, `JBUI.CurrentTheme`, `NamedColorUtil`, `JBColor.namedColor`, or `JBColor.lazy`. +- Do not use hardcoded font sizes or font families in generated UI examples or implementation code. +- Use component defaults, `JBFont`, `RelativeFont`, or existing component fonts for typography. +- Put user-visible strings in `*.properties` files. + +## Decision Tree + +- Use Kotlin UI DSL v2 for dialogs, settings pages, forms, options panels, and structured component layouts. +- Use Kotlin UI DSL v2 inside tool windows when rendering form-like controls or structured subpanels. +- Use standard Swing with IntelliJ Platform component replacements for tool-window shells, action-driven UI, custom components, transcript surfaces, and custom renderers. +- Use the Action System for menus and toolbars. +- Do not use Kotlin Compose. +- Do not use JCEF. + +| Need | API | +|---|---| +| Dialogs, settings pages, forms, any layout with components | Preferred: Kotlin UI DSL v2 (`com.intellij.ui.dsl.builder`) | +| Tool window panels, action-driven UI, custom components | Standard Swing with IntelliJ Platform component replacements | +| Menus and toolbars | Action System | + +## Minimal Code Rule + +Avoid explicit defaults and manual layout when the DSL can express the UI: + +```kotlin +// Avoid +val panel = JPanel(BorderLayout()).apply { + isOpaque = false + preferredSize = Dimension(420, 260) + border = EmptyBorder(12, 12, 12, 12) +} +``` + +Prefer DSL and platform spacing: + +```kotlin +panel { + row(KiloBundle.message("settings.name")) { + textField() + .align(AlignX.FILL) + .resizableColumn() + } +} +``` + +Avoid explicit default properties: + +```kotlin +// Avoid unless there is a documented rendering reason +component.isOpaque = false +``` + +Prefer omitting the assignment and relying on the component/platform default. + +Avoid raw fixed sizes: + +```kotlin +// Avoid +component.preferredSize = Dimension(300, 40) +``` + +Prefer semantic sizing: + +```kotlin +textField() + .columns(COLUMNS_MEDIUM) + .align(AlignX.FILL) +``` + +If a fixed size is genuinely required, use `JBUI.size(...)` or `JBUI.scale(...)` and keep the reason obvious from context. + +## Do Not Use Kotlin Compose + +Do not use Kotlin Compose or `intellij.platform.compose` in this plugin. The JetBrains modular template uses Compose for its demo tool window, but Kilo should use standard Swing with IntelliJ Platform components only. Keep all plugin UI in the existing Swing-based stack. + +## Do Not Use JCEF + +Do not use JCEF (`JBCefBrowser`) in this plugin. JCEF does not work in JetBrains remote development (split mode): the frontend process runs on the client machine but JCEF requires a display on the host, making it effectively unusable for remote users. Use standard Swing with IntelliJ Platform components for all UI. + +## Kotlin UI DSL v2 + +Use Kotlin UI DSL v2 as the default way to build UI for dialogs, settings pages, forms, and any layout composed of standard components. It produces correct spacing, label alignment, HiDPI scaling, and accessibility automatically. Only fall back to manual Swing layout when you need a fully custom component, such as a canvas, rich list renderer, transcript surface, or tool-window chrome that the DSL cannot express. + +The top-level builder is `panel { }` and returns `DialogPanel`. Structure is `panel -> row -> cells`. Cell factory methods such as `textField()`, `checkBox()`, and `label()` add components. The DSL lives in `com.intellij.ui.dsl.builder`. + +To explore DSL capabilities interactively: Tools -> Internal Actions -> UI -> Kotlin UI DSL -> UI DSL Showcase. This requires internal mode: `-Didea.is.internal=true`. + +### Basics: Panel, Row, Cell + +Rows occupy full width. The last cell in a row takes remaining space. Rows have a `layout` property. + +```kotlin +panel { + row("Row1 label:") { + textField() + label("Some text") + } + row("Row2:") { + label("This text is aligned with previous row") + } +} +``` + +### Row Layout + +Every row uses one of three layouts. Default is `LABEL_ALIGNED` when a label is provided for the row, `INDEPENDENT` otherwise. + +| Layout | Behavior | +|---|---| +| `LABEL_ALIGNED` | Label column and content columns, aligned across rows | +| `INDEPENDENT` | All cells are independent, no cross-row alignment | +| `PARENT_GRID` | Cells align with the parent grid columns across rows | + +```kotlin +panel { + row("PARENT_GRID:") { + label("Col 1") + label("Col 2") + }.layout(RowLayout.PARENT_GRID) + + row("PARENT_GRID:") { + textField() + textField() + }.layout(RowLayout.PARENT_GRID) + + row("LABEL_ALIGNED default with label:") { + textField() + } + + row { + label("INDEPENDENT default without label:") + textField() + } +} +``` + +### Components Reference + +All cell factory methods available inside `row { }`: + +| Method | Description | +|---|---| +| `checkBox("text")` | Checkbox | +| `threeStateCheckBox("text")` | Three-state checkbox | +| `radioButton("text", value)` | Radio button, must be inside `buttonsGroup {}` | +| `button("text") {}` | Push button | +| `actionButton(action)` | Icon button bound to an `AnAction` | +| `actionsButton(action1, action2, ...)` | Dropdown actions button | +| `segmentedButton(items) { text = it }` | Segmented control | +| `tabbedPaneHeader(items)` | Tab header strip | +| `label("text")` | Static label | +| `text("html")` | Rich text with links, icons, line-width control | +| `link("text") {}` | Focusable clickable link | +| `browserLink("text", "url")` | Opens URL in browser | +| `dropDownLink("default", listOf(...))` | Dropdown link selector | +| `icon(AllIcons.*)` | Icon display | +| `contextHelp("description", "title")` | Help icon with popup | +| `textField()` | Text input | +| `passwordField()` | Password input | +| `textFieldWithBrowseButton()` | Text field and browse dialog | +| `expandableTextField()` | Expandable multi-line text field | +| `extendableTextField()` | Text field with extension icons | +| `intTextField(range)` | Integer input with validation | +| `spinner(intRange)` / `spinner(doubleRange, step)` | Numeric spinner | +| `slider(min, max, minorTick, majorTick)` | Slider, use `.labelTable()` for tick labels | +| `textArea()` | Multi-line text, use `.rows(n)` and `.align(AlignX.FILL)` | +| `comboBox(items)` | Combo box / dropdown | +| `comment("text")` | Gray comment text, standalone | +| `cell(component)` | Wrap any arbitrary Swing component | +| `scrollCell(component)` | Wrap component in a scroll pane | +| `cell()` | Empty placeholder cell for grid alignment | + +Key component examples: + +```kotlin +panel { + var mode = "auto" + + buttonsGroup { + row("Mode:") { + radioButton("Automatic", "auto") + radioButton("Manual", "manual") + } + }.bind({ mode }, { mode = it }) + + row("Slider:") { + slider(0, 10, 1, 5) + .labelTable(mapOf( + 0 to JBLabel("0"), + 5 to JBLabel("5"), + 10 to JBLabel("10"), + )) + } + + row { + label("Text area:") + .align(AlignY.TOP) + .gap(RightGap.SMALL) + textArea() + .rows(5) + .align(AlignX.FILL) + }.layout(RowLayout.PARENT_GRID) +} +``` + +### Component Labels + +Labels for modifiable components must be connected via one of two methods. This ensures correct spacing, mnemonic support, and accessibility. + +- Row label: `row("&Label:") { textField() }`, mnemonic via `&`, label in left column +- Cell label: `textField().label("&Label:", LabelPosition.TOP)`, label attached to cell, optionally on top + +```kotlin +panel { + row("&Row label:") { + textField() + textField() + .label("Cell label at &left:") + } + row { + textField() + .label("Cell label at &top:", LabelPosition.TOP) + } +} +``` + +When a row contains a `checkBox` or `radioButton`, the DSL automatically increases space after the row label per IntelliJ UI Guidelines. + +### Comments + +Three types of comments, each with different placement and semantics: + +| Type | Method | Placement | +|---|---|---| +| Cell comment, bottom | `cell.comment("text")` | Below the cell | +| Cell comment, right | `cell.commentRight("text")` | Right of the cell | +| Cell context help | `cell.contextHelp("text", "title")` | Help icon with popup | +| Row comment | `row.rowComment("text")` | Below the entire row | +| Arbitrary comment | `comment("text")` | Standalone gray text | + +```kotlin +panel { + row { + textField() + .comment("Bottom comment") + textField() + .commentRight("Right comment") + textField() + .contextHelp("Help popup text") + } + + row("Label:") { + textField() + }.rowComment("This comment sits below the whole row") + + row { + comment("Standalone comment, supports links and  icons") + } +} +``` + +Comments support HTML with clickable links, bundled icons via ``, and line width control via `maxLineLength`. Use `MAX_LINE_LENGTH_NO_WRAP` to prevent wrapping. + +### Groups and Structure + +| Method | Grid | Description | +|---|---|---| +| `panel {}` | Own grid | Sub-panel occupying full width | +| `rowsRange {}` | Parent grid | Grouped rows sharing parent grid, useful with `enabledIf` | +| `group("Title") {}` | Own grid | Titled section with vertical spacing before/after | +| `groupRowsRange("Title") {}` | Parent grid | Titled section sharing parent grid alignment | +| `collapsibleGroup("Title") {}` | Own grid | Expandable section, Tab-focusable, supports mnemonics | +| `buttonsGroup("Title") {}` | None | Groups `radioButton` or `checkBox` under a title | +| `separator()` | None | Horizontal separator line | +| Row `panel {}` | Own grid | Sub-panel inside a cell | + +```kotlin +panel { + group("Settings") { + row("Name:") { textField() } + row("Path:") { textFieldWithBrowseButton() } + } + + collapsibleGroup("Advanced") { + row("Timeout:") { intTextField(0..1000) } + } + + var enabled = true + buttonsGroup("Mode:") { + row { radioButton("Automatic", true) } + row { radioButton("Manual", false) } + }.bind({ enabled }, { enabled = it }) + + separator() + + row { + label("Nested panels:") + panel { + row("Sub row 1:") { textField() } + row("Sub row 2:") { textField() } + } + } +} +``` + +### Gaps and Spacing + +- Horizontal gaps: `cell.gap(RightGap.SMALL)` between a label-like checkbox and its related field. Medium gap is the default between cells. +- Two-column layout: `twoColumnsRow({}, {})` or `gap(RightGap.COLUMNS)` with `.layout(RowLayout.PARENT_GRID)`. +- Left indent: `indent {}` for indented sub-content. +- Vertical gaps: `.topGap(TopGap.MEDIUM)` / `.bottomGap(BottomGap.MEDIUM)` on rows to separate unrelated groups. Attach gaps to the related row so hiding rows does not break layout. + +```kotlin +panel { + group("Horizontal Gaps") { + row { + val cb = checkBox("Use mail:") + .gap(RightGap.SMALL) + textField() + .enabledIf(cb.selected) + } + row("Width:") { + textField() + .gap(RightGap.SMALL) + label("pixels") + } + } + + group("Indent") { + row { label("Not indented") } + indent { + row { label("Indented row") } + } + } + + group("Two Columns") { + twoColumnsRow({ + checkBox("First column") + }, { + checkBox("Second column") + }) + } + + group("Vertical Gaps") { + row { checkBox("Option 1") } + row { checkBox("Option 2") } + row { checkBox("Unrelated option") } + .topGap(TopGap.MEDIUM) + } +} +``` + +### Enabled and Visible State + +Bind enabled/visible state to a checkbox or other observable. Works on rows, `indent {}` blocks, `rowsRange {}`, and individual cells. + +```kotlin +panel { + group("Enabled") { + lateinit var cb: Cell + row { cb = checkBox("Enable options") } + indent { + row { checkBox("Option 1") } + row { checkBox("Option 2") } + }.enabledIf(cb.selected) + } + + group("Visible") { + lateinit var cb: Cell + row { cb = checkBox("Show options") } + indent { + row { checkBox("Option 1") } + row { checkBox("Option 2") } + }.visibleIf(cb.selected) + } +} +``` + +### Binding + +Bind component values to model properties. Values are applied on `DialogPanel.apply()`, checked with `.isModified()`, and reverted with `.reset()`. + +| Method | Component | +|---|---| +| `bindSelected(model::prop)` | checkBox | +| `bindText(model::prop)` | textField | +| `bindIntText(model::prop)` | intTextField | +| `bindItem(model::prop.toNullableProperty())` | comboBox | +| `bindValue(model::prop)` | slider | +| `bindIntValue(model::prop)` | spinner | +| `buttonsGroup {}.bind(model::prop)` | radio group | + +```kotlin +enum class Theme { LIGHT, DARK } + +data class Settings( + var name: String = "", + var count: Int = 0, + var enabled: Boolean = false, + var theme: Theme = Theme.LIGHT, +) + +val model = Settings() + +val panel = panel { + row("Name:") { + textField().bindText(model::name) + } + row("Count:") { + intTextField(0..100).bindIntText(model::count) + } + row { + checkBox("Enabled").bindSelected(model::enabled) + } + buttonsGroup("Theme:") { + row { radioButton("Light", Theme.LIGHT) } + row { radioButton("Dark", Theme.DARK) } + }.bind(model::theme) +} + +panel.isModified() +panel.apply() +panel.reset() +``` + +### Validation + +Attach input validation rules to cells. Rules run continuously and display inline error/warning indicators. + +```kotlin +panel { + row("Username:") { + textField() + .columns(COLUMNS_MEDIUM) + .cellValidation { + addInputRule("Must not be empty") { + it.text.isBlank() + } + } + } + row("Port:") { + textField() + .columns(COLUMNS_MEDIUM) + .cellValidation { + addInputRule("Contains non-numeric characters", level = Level.WARNING) { + it.text.contains(Regex("[^0-9]")) + } + } + } +} +``` + +Activate validators by calling `dialogPanel.registerValidators(disposable)` after creating the panel. + +### Tips and Common Patterns + +| Pattern | Usage | +|---|---| +| `.bold()` | Bold text on any cell | +| `.columns(COLUMNS_MEDIUM)` | Set preferred width of textField / comboBox / textArea | +| `.text("initial")` | Set initial text on text components | +| `.resizableColumn()` | Column fills remaining horizontal space | +| `cell()` | Empty placeholder cell for grid alignment | +| `.widthGroup("name")` | Equalize widths across rows, cannot combine with `AlignX.FILL` | +| `.align(AlignX.FILL)` | Stretch component to fill available width | +| `.align(AlignY.TOP)` | Top-align component in its cell | +| `.applyToComponent { }` | Direct access to the underlying Swing component | +| `.selected(true)` | Default-select a radioButton when no bound value matches | +| `.gap(RightGap.COLUMNS)` | Column-level gap for multi-column layouts | + +```kotlin +panel { + row { label("Title").bold() } + + row("Name:") { + textField() + .columns(COLUMNS_MEDIUM) + .resizableColumn() + .align(AlignX.FILL) + } + + row("") { + textField() + }.rowComment("""Use row("") for an empty label column that aligns with labeled rows""") + + row { + text("Comment-colored text") + .applyToComponent { foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND } + } +} +``` + +## Manual Swing + +Use manual Swing only when Kotlin UI DSL cannot express the UI cleanly. + +Preferred manual Swing rules: + +- Use IntelliJ platform components instead of raw Swing components. +- Use layout managers and component defaults rather than fixed sizes. +- Use `JBUI.Borders.empty(...)`, `JBUI.insets(...)`, `JBUI.size(...)`, and `JBUI.scale(...)` instead of raw AWT/Swing sizing primitives. +- Do not set default properties explicitly. +- Keep custom components small and focused. +- For painting, use theme-aware colors and `JBUI.scale(...)` for pixel values. + +## Platform Spacing and Insets + +Use semantic spacing APIs before inventing numbers. Do not copy fallback values from IntelliJ source into generated UI code; those values are defaults behind theme keys and may change by UI theme, New UI, OS, or IDE version. + +| Need | Preferred source | +|---|---| +| Dialogs, forms, settings | Kotlin UI DSL row/group/indent/gap APIs | +| Component gaps in Kotlin UI DSL | `RightGap.SMALL`, `RightGap.COLUMNS`, `TopGap.MEDIUM`, `BottomGap.MEDIUM`, `.customize(UnscaledGaps(...))` as a last resort | +| Manual Swing empty padding | `JBUI.Borders.empty(...)` | +| Manual Swing insets | `JBUI.insets(...)`, `JBUI.emptyInsets()`, `JBUI.insetsTop(...)`, `JBUI.insetsLeft(...)`, `JBUI.insetsBottom(...)`, `JBUI.insetsRight(...)` | +| Manual Swing dimensions | `JBUI.size(...)`, `JBDimension`, `JBUI.scale(...)` | +| Side separators | `JBUI.Borders.customLineTop(...)`, `customLineBottom(...)`, `customLineLeft(...)`, `customLineRight(...)` | +| Composed borders | `JBUI.Borders.compound(...)`, `JBUI.Borders.merge(...)` | +| Simple `BorderLayout` panels | `JBUI.Panels.simplePanel(...)`, `BorderLayoutPanel` | +| Simple vertical custom Swing groups | `VerticalLayout` | +| Fluent platform panels | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()`, `.withBackground(...)` | + +Use `JBUI.CurrentTheme` for context-specific spacing and dimensions: + +| UI area | Preferred source | +|---|---| +| Action list rows | `JBUI.CurrentTheme.ActionsList.cellPadding()` | +| Action icon/text gaps | `JBUI.CurrentTheme.ActionsList.elementIconGap()` | +| Action mnemonic gaps | `JBUI.CurrentTheme.ActionsList.mnemonicIconGap()` / `mnemonicInsets()` | +| Popup selection interior | `JBUI.CurrentTheme.Popup.Selection.innerInsets()` | +| Popup separators | `JBUI.CurrentTheme.Popup.separatorInsets()` / `separatorLabelInsets()` | +| Popup header/search field | `JBUI.CurrentTheme.Popup.headerInsets()`, `searchFieldBorderInsets()`, `searchFieldInputInsets()` | +| Complex popup headers | `JBUI.CurrentTheme.ComplexPopup.headerInsets()` | +| Complex popup text fields | `JBUI.CurrentTheme.ComplexPopup.textFieldBorderInsets()` / `textFieldInputInsets()` | +| Search Everywhere / big popup header | `JBUI.CurrentTheme.BigPopup.headerBorder()` / `headerToolbarInsets()` / `tabInsets()` | +| Popup advertiser/footer | `JBUI.CurrentTheme.Advertiser.border()` | +| Toolbar buttons | `JBUI.CurrentTheme.Toolbar.toolbarButtonInsets()` / `mainToolbarButtonInsets()` | +| Toolbar containers | `JBUI.CurrentTheme.Toolbar.horizontalToolbarInsets()` / `verticalToolbarInsets()` | +| Tool-window headers | `JBUI.CurrentTheme.ToolWindow.headerLabelLeftRightInsets()` / `headerToolbarLeftRightInsets()` / `headerTabLeftRightInsets()` | +| Lists | `JBUI.CurrentTheme.List.rowHeight()` | +| Trees | `JBUI.CurrentTheme.Tree.rowHeight()` | +| VCS log rows | `JBUI.CurrentTheme.VersionControl.Log.rowHeight()` / `verticalPadding()` | +| Help tooltips | `JBUI.CurrentTheme.HelpTooltip.defaultTextBorderInsets()` / `smallTextBorderInsets()` | +| Navigation bar items | `JBUI.CurrentTheme.NavBar.itemInsets()` | + +When using Kotlin UI DSL, prefer DSL semantics over manual padding: + +```kotlin +panel { + group(KiloBundle.message("settings.section")) { + row(KiloBundle.message("settings.name")) { + textField() + .align(AlignX.FILL) + .resizableColumn() + } + indent { + row { checkBox(KiloBundle.message("settings.enabled")) } + } + } +} +``` + +When manual Swing is necessary, get padding from platform utilities: + +```kotlin +val panel = JBUI.Panels.simplePanel(content) + .withBorder(JBUI.Borders.empty(JBUI.CurrentTheme.Popup.headerInsets())) +``` + +For row heights, prefer platform row height APIs over fixed heights: + +```kotlin +component.preferredSize = JBDimension(0, JBUI.CurrentTheme.Tree.rowHeight()) +``` + +## Generic Platform Utilities + +Use existing platform utility classes instead of hand-rolled labels, renderers, borders, colors, fonts, and validation behavior. + +| Need | Preferred API | +|---|---| +| Concise border layout | `BorderLayoutPanel`, `JBUI.Panels.simplePanel(...)` | +| Platform panel helpers | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()` | +| Platform label behavior | `JBLabel` | +| Context help | `ContextHelpLabel` | +| Links | `HyperlinkLabel`, `LinkLabel`, Kotlin UI DSL `link(...)` / `browserLink(...)` | +| Error/validation label in legacy UI | `ErrorLabel` only when an inline validation component is required and DSL validation is not available | +| High-performance rich fragments | `SimpleColoredComponent` | +| List renderers | `ColoredListCellRenderer`, Kotlin `listCellRenderer` / `textListCellRenderer` / `LcrRow` | +| Tree renderers | `ColoredTreeCellRenderer` | +| Renderer text styles | `SimpleTextAttributes` | +| Editable list toolbar | `ToolbarDecorator` | +| Platform list | `JBList` | +| Platform tree | `Tree` | + +For renderer text, prefer `SimpleTextAttributes` over direct color/font mutation: + +```kotlin +append(text, SimpleTextAttributes.ERROR_ATTRIBUTES) +append(detail, SimpleTextAttributes.GRAYED_ATTRIBUTES) +append(link, SimpleTextAttributes.LINK_ATTRIBUTES) +``` + +## Tool Windows + +- Register declaratively in module XML via `com.intellij.toolWindow` extension point. +- Implement `ToolWindowFactory.createToolWindowContent()`, called lazily on first click. +- Use `SimpleToolWindowPanel(vertical = true)` as a convenient base for toolbar and content layout. +- Add tabs via `ToolWindow.contentManager`: create content with `ContentFactory.getInstance().createContent(component, title, isLockable)`, then `contentManager.addContent()`. +- For conditional display, implement `ToolWindowFactory.isApplicableAsync(project)`. +- Always use `ToolWindowManager.invokeLater()` instead of `Application.invokeLater()` for tool-window-related EDT tasks. + +## Dialogs + +- Extend `DialogWrapper`. +- Call `init()` from the constructor. +- Override `createCenterPanel()` to return UI content. +- Prefer Kotlin UI DSL v2 for panel contents. +- Override `getPreferredFocusedComponent()` for initial focus. +- Override `getDimensionServiceKey()` for size persistence when useful. +- Show with `showAndGet()` for modal boolean result, or `show()` and then `getExitCode()`. +- For input validation, call `initValidation()` in the constructor and override `doValidate()` to return `null` if valid or `ValidationInfo(message, component)` if invalid. + +## Platform Components + +Always use IntelliJ platform components instead of raw Swing where an equivalent exists. + +| Instead of | Use | Package | +|---|---|---| +| `JLabel` | `JBLabel` | `com.intellij.ui.components` | +| `JTextField` | `JBTextField` | `com.intellij.ui.components` | +| `JTextArea` | `JBTextArea` | `com.intellij.ui.components` | +| `JList` | `JBList` | `com.intellij.ui.components` | +| `JScrollPane` | `JBScrollPane` | `com.intellij.ui.components` | +| `JTable` | `JBTable` | `com.intellij.ui.table` | +| `JTree` | `Tree` | `com.intellij.ui.treeStructure` | +| `JSplitPane` | `JBSplitter` | `com.intellij.ui` | +| `JTabbedPane` | `JBTabs` | `com.intellij.ui.tabs` | +| `JCheckBox` | `JBCheckBox` | `com.intellij.ui.components` | +| Raw runtime colors | `UIUtil`, `JBUI.CurrentTheme`, `NamedColorUtil`, `JBColor.namedColor`, `JBColor.lazy` | `com.intellij.util.ui`, `com.intellij.ui` | +| `EmptyBorder` | `JBUI.Borders.empty()` | `com.intellij.util.ui` | +| Hardcoded pixel sizes | `JBUI.scale(px)` | `com.intellij.util.ui` | + +Inspection `Plugin DevKit | Code | Undesirable class usage` highlights raw Swing usage where a platform replacement exists. + +## Multi-line and Rich Text + +| Need | Component | +|---|---| +| Rich HTML with modern CSS, icons, shortcuts | `JBHtmlPane` (`com.intellij.ui.components.JBHtmlPane`) | +| Simple multi-line label with HTML | `JBLabel` + `XmlStringUtil.wrapInHtml()` | +| Scrollable / wrapping HTML panel | `SwingHelper.createHtmlViewer()` | +| High-performance colored text fragments in trees/lists/tables | `SimpleColoredComponent` | +| Plain-text newline splitting | `MultiLineLabel`, legacy, do not use in new code | + +- Build HTML programmatically with `HtmlChunk` / `HtmlBuilder` (`com.intellij.openapi.util.text.HtmlChunk`). Avoid raw HTML string concatenation because it risks injection and breaks localization. +- For simple wrapping/escaping, use `XmlStringUtil.wrapInHtml(content)`, `XmlStringUtil.wrapInHtmlLines(lines...)`, and `XmlStringUtil.escapeString(text)`. +- For selectable/copyable label text, use `JBLabel.setCopyable(true)`, which switches internally to `JEditorPane` while preserving label appearance. Use `setAllowAutoWrapping(true)` for auto-wrap. +- When creating a `JEditorPane` manually, always use `HTMLEditorKitBuilder` instead of constructing `HTMLEditorKit` directly: `editorPane.setEditorKit(HTMLEditorKitBuilder.simple())` or `.withWordWrapViewFactory().build()`. +- For single-line overflow/ellipsis, use `SwingTextTrimmer`. Do not manually truncate strings. +- Put all user-visible strings in `*.properties` files. HTML markup in values is acceptable. + +## Theme-Derived Colors + +Do not hardcode runtime colors. IntelliJ UI colors must come from the current theme, component state, editor color scheme, or a centralized semantic named color key. + +Prefer semantic helpers for common UI roles: + +| Need | Preferred API | +|---|---| +| Ordinary label text | `UIUtil.getLabelForeground()` | +| Secondary/help text | `UIUtil.getContextHelpForeground()` | +| Info label text | `UIUtil.getLabelInfoForeground()` | +| Success label text | `UIUtil.getLabelSuccessForeground()` | +| Error label text | `UIUtil.getErrorForeground()` | +| Warning label text | `JBUI.CurrentTheme.Label.warningForeground()` | +| Generic label foreground by state | `JBUI.CurrentTheme.Label.foreground(selected)` / `disabledForeground(selected)` | +| Inactive secondary text | `NamedColorUtil.getInactiveTextColor()` | +| Bounds and standard border color | `NamedColorUtil.getBoundsColor()` / `JBColor.border()` | +| Tooltips | `UIUtil.getToolTipForeground()` / `getToolTipBackground()` / `JBUI.CurrentTheme.Tooltip.*` | +| Links | `JBUI.CurrentTheme.Link.Foreground.ENABLED` / `HOVERED` / `PRESSED` / `VISITED` / `DISABLED` / `SECONDARY` | +| List renderer text/background | `UIUtil.getListForeground(selected, focused)` / `UIUtil.getListBackground(selected, focused)` | +| Tree renderer text/background | `UIUtil.getTreeForeground(selected, focused)` / `UIUtil.getTreeBackground(selected, focused)` | +| Non-selected tree content | `UIUtil.getTreeForeground()` / `UIUtil.getTreeBackground()` | + +Prefer `JBUI.CurrentTheme` for component-area colors and borders: + +| Need | Preferred API | +|---|---| +| Popup background | `JBUI.CurrentTheme.Popup.BACKGROUND` | +| Complex popup header | `JBUI.CurrentTheme.ComplexPopup.HEADER_BACKGROUND` | +| Separators | `JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground()` | +| Advertiser/footer surfaces | `JBUI.CurrentTheme.Advertiser.foreground()` / `background()` / `border()` | +| Links | `JBUI.CurrentTheme.Link.Foreground.ENABLED` | +| Tree selection state | `JBUI.CurrentTheme.Tree.foreground(...)` and selection helpers | +| Validation errors | `JBUI.CurrentTheme.Validator.errorBorderColor()` / `errorBackgroundColor()` | +| Validation warnings | `JBUI.CurrentTheme.Validator.warningBorderColor()` / `warningBackgroundColor()` | +| Focus error/warning outlines | `JBUI.CurrentTheme.Focus.errorColor(active)` / `warningColor(active)` | +| Banner-like surfaces | `JBUI.CurrentTheme.Banner.*` | +| Notification tool-window surfaces | `JBUI.CurrentTheme.NotificationError.*`, `NotificationWarning.*`, `NotificationInfo.*` | +| Icon badges | `JBUI.CurrentTheme.IconBadge.*` | + +Use `JBColor.lazy { ... }` for colors that depend on runtime state or the active editor color scheme: + +```kotlin +val bg = JBColor.lazy { EditorColorsManager.getInstance().globalScheme.defaultBackground } +``` + +Use `JBColor.namedColor("Some.Semantic.Key", fallback)` only when defining or consuming a semantic color key. Prefer a fallback from an existing theme API over numeric RGB values in examples. + +For HTML/CSS snippets, compute the color from a theme API and convert it with `ColorUtil.toHtmlColor(...)`. Do not write hardcoded CSS color literals. + +```kotlin +val fg = ColorUtil.toHtmlColor(UIUtil.getContextHelpForeground()) +val html = HtmlChunk.span("color: $fg").addText(text) +``` + +Recommended examples: + +```kotlin +label.applyToComponent { + foreground = UIUtil.getContextHelpForeground() + font = JBFont.medium() +} +``` + +```kotlin +foreground = if (selected) { + UIUtil.getTreeForeground(true, hasFocus) +} else { + UIUtil.getContextHelpForeground() +} +``` + +Avoid inline `Color(...)`, numeric `JBColor(...)`, `Gray.xNN`, `JBColor.GRAY`, and hex color literals in runtime UI code. The exception is a centralized semantic color definition with a named color key when no existing platform key exists. + +## Theme-Derived Fonts + +Do not hardcode font sizes or font families. IntelliJ fonts must come from component defaults, platform typography helpers, or existing component fonts. + +- Prefer component default fonts when no style change is needed. +- Use `JBFont.h1()`, `JBFont.h2()`, `JBFont.h3()`, and `JBFont.h4()` for headings. +- Use `.asBold()`, `.asItalic()`, and `.asPlain()` for style changes on `JBFont` values. +- Use `JBFont.regular()`, `JBFont.medium()`, and `JBFont.small()` for regular and secondary text. +- Use `UIUtil.getLabelFont(UIUtil.FontSize.SMALL)` / `MINI` only when an API expects a raw `Font` and `JBFont` / `RelativeFont` is not a good fit. +- Use `RelativeFont` when adjusting an existing component font relatively. +- Use `RelativeFont.fromResource(...)` for theme-configurable font offsets when matching platform component patterns. +- Use `JBUI.Fonts.smallFont()` only when matching an existing platform component pattern that expects it. +- If editor-like text needs editor typography, derive it from the editor color scheme or existing editor component instead of hardcoding a font. + +For errors, grayed text, shortcuts, and links in renderers, prefer `SimpleTextAttributes.ERROR_ATTRIBUTES`, `GRAYED_ATTRIBUTES`, `SHORTCUT_ATTRIBUTES`, and `LINK_ATTRIBUTES` rather than manually selecting colors and styles. + +Recommended examples: + +```kotlin +label(title).applyToComponent { + font = JBFont.h3().asBold() +} +``` + +```kotlin +RelativeFont.BOLD.install(label) +``` + +Avoid `Font("...")`, raw font sizes, and `deriveFont(14f)` style examples. + +## Borders, Insets, and Spacing + +- Always create borders via `JBUI.Borders.empty(top, left, bottom, right)` and insets via `JBUI.insets()` so they are DPI-aware and auto-update on zoom. +- Use `JBUI.scale(int)` for any pixel dimension to ensure proper HiDPI scaling. +- Prefer Kotlin UI DSL gaps and row layout semantics over manually assigning borders or insets. +- Do not use `EmptyBorder`, raw `Insets`, or raw `Dimension` unless there is no platform alternative and the reason is obvious. + +## Validation and Error UI + +Prefer built-in validation mechanisms over custom error rendering. + +- For Kotlin UI DSL, use `validationOnInput`, `validationOnApply`, `validationInfo`, `errorOnApply`, and `addValidationRule` on cells. +- For DSL dialogs and settings panels, call `DialogPanel.registerValidators(disposable)` when input validation should run continuously. +- Call `DialogPanel.validateAll()` before `apply()` when submitting a DSL panel manually. +- In `DialogWrapper`, use `doValidate()` and return `ValidationInfo(message, component)` for errors. +- Use `ValidationInfo.asWarning()` for warnings and `withOKEnabled()` when a warning should not block submission. +- For hand-built Swing forms, use `ComponentValidator` with `withValidator`, `withFocusValidator`, and `andRegisterOnDocumentListener` instead of custom tooltip/error border logic. +- If custom validation painting is unavoidable, use `JBUI.CurrentTheme.Validator.*` and `JBUI.CurrentTheme.Focus.*` colors. + +Kotlin UI DSL example: + +```kotlin +textField() + .validationOnInput { + if (it.text.isBlank()) error(KiloBundle.message("settings.name.required")) else null + } +``` + +Dialog validation example: + +```kotlin +override fun doValidate(): ValidationInfo? { + if (field.text.isBlank()) return ValidationInfo(KiloBundle.message("settings.name.required"), field) + return null +} +``` + +## Icons + +- Reuse platform icons. Browse at https://intellij-icons.jetbrains.design and access via `AllIcons.*` constants. +- Custom icons belong in `resources/icons/`. +- Load custom icons via `IconLoader.getIcon("/icons/foo.svg", MyClass::class.java)`. +- Organize custom icons in an `icons` package or a `*Icons` object with `@JvmField` on each constant. +- Icon sizing guideline values: actions/nodes = 16x16, tool window = 13x13 classic or 20x20 + 16x16 compact New UI, editor gutter = 12x12 classic or 14x14 New UI. +- Dark variants: `icon.svg` + `icon_dark.svg`. +- HiDPI variants: `icon@2x.svg` + `icon@2x_dark.svg`. +- New UI support: place New UI icons in `expui/`, create `*IconMappings.json`, register via `com.intellij.iconMapper` extension point. +- For SVG asset palette values, follow the IntelliJ icon asset guidelines. Do not copy guideline color literals into runtime Swing UI code. + +## Notifications + +- Declare in module XML: ``. +- Show with `Notification("Kilo Code", "message", NotificationType.INFORMATION).notify(project)`. +- Add actions with `.addAction(NotificationAction.createSimpleExpiring("Label") { ... })`. +- Sticky notifications use `displayType="STICKY_BALLOON"` and `.setSuggestionType(true)`. +- Tool-window-bound notifications use `displayType="TOOL_WINDOW" toolWindowId="Kilo Code"`. +- Prefer non-modal notifications over `Messages.show*()` dialogs. + +## Popups + +- Use `JBPopupFactory.getInstance()` for lightweight floating UI with no chrome and auto-dismiss on focus loss. +- Use `createComponentPopupBuilder(component, focusable)` for arbitrary Swing content. +- Use `createPopupChooserBuilder(list)` for item selection. +- Use `createActionGroupPopup()` for action menus. +- Show with `showInBestPositionFor(editor)`, `showUnderneathOf(component)`, or `showInCenterOf(component)`. + +## Lists and Trees + +- Use `JBList`, not `JList`, for empty text, busy indicator, and tooltip truncation. +- Use `Tree`, not `JTree`, for wide selection painting and auto-scroll on drag-and-drop. +- Use `ColoredListCellRenderer` / `ColoredTreeCellRenderer` for custom renderers. +- Use `append()` for styled text and `setIcon()` for icons. +- Use `ListSpeedSearch(list)` / `TreeSpeedSearch(tree)` for speed search. +- Use `ToolbarDecorator.createDecorator(list).setAddAction { }.setRemoveAction { }.createPanel()` for editable lists with add/remove/reorder toolbar. + +## Before Returning Code + +Review generated UI code and remove: + +- Explicit default property assignments such as unnecessary `isOpaque = false` +- Unnecessary `preferredSize`, `minimumSize`, or `maximumSize` +- Raw `Dimension`, `Insets`, `EmptyBorder`, or `Color` +- Inline runtime colors such as `Color(...)`, numeric `JBColor(...)`, `Gray.xNN`, `JBColor.GRAY`, or hex color literals +- Raw CSS color literals; `ColorUtil.toHtmlColor(themeColor)` is allowed because the source color is theme-derived +- Hardcoded font families, raw font sizes, or numeric-size `deriveFont(...)` calls +- Raw Swing components where IntelliJ replacements exist +- Manual layout code that Kotlin UI DSL can express cleanly +- Hardcoded spacing that should be a DSL gap, row gap, or `JBUI` value +- Extra helpers that do not make the UI clearer or more reusable + +## References + +- IntelliJ Platform UI Guidelines: https://jetbrains.design/intellij/ +- User Interface Components: https://plugins.jetbrains.com/docs/intellij/user-interface-components.html +- UI FAQ: https://plugins.jetbrains.com/docs/intellij/ui-faq.html +- Kotlin UI DSL v2: https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl-version-2.html +- Split mode for remote development: https://plugins.jetbrains.com/docs/intellij/split-mode-for-remote-development.html diff --git a/AGENTS.md b/AGENTS.md index 104ffc67032..74dc60eceb4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ Before saying an implementation is ready, run the smallest relevant checks that | CLI | From `packages/opencode/`: `bun run typecheck`, `bun test` or targeted `bun test ./path/to/file.test.ts` | | VS Code extension | From `packages/kilo-vscode/`: `bun run typecheck`, `bun run lint`, `bun run test:unit` or `bun run test` | | Extension build/package | From `packages/kilo-vscode/`: `bun run compile` or `bun run package` when touching build, packaging, SDK, or webview integration paths | -| CI-only guards | Run affected guards documented above, such as `bun run knip`, `bun run check-kilocode-change`, source link extraction, or opencode annotation checks | +| CI-only guards | Run affected guards documented above, such as `bun run knip`, `bun run check-kilocode-change`, `bun run script/check-opencode-annotations.ts`, or source link extraction | Never run root `bun test`; the root script prints `do not run tests from root` and exits with code 1. Use package-level tests instead. @@ -50,6 +50,8 @@ All products are clients of the **CLI** (`packages/opencode/`), which contains t **Agent Manager** refers to a feature inside `packages/kilo-vscode/` (extension code in `src/agent-manager/`, webview in `webview-ui/agent-manager/`). It is not a standalone product. See the extension's `AGENTS.md` for details. +Extension-specific settings should live in the Kilo extension settings, not default VS Code settings, unless they are intentionally VS Code-wide. + ## Monorepo Structure Turborepo + Bun workspaces. The packages you'll work with most: diff --git a/bun.lock b/bun.lock index f2bf0dfaf4b..c41c4b11945 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,7 @@ "@changesets/changelog-github": "^0.5.1", "@changesets/cli": "^2.27.10", "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", "glob": "13.0.5", @@ -32,14 +33,13 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@kilocode/kilo-i18n": "workspace:*", "@kilocode/kilo-ui": "workspace:*", "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", @@ -108,6 +108,7 @@ "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", + "rotating-file-stream": "3.2.9", "semver": "^7.6.3", "xdg-basedir": "5.1.0", "zod": "catalog:", @@ -123,7 +124,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -156,7 +157,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -208,7 +209,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -237,7 +238,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", @@ -273,7 +274,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.2.30", + "version": "7.2.34", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -283,7 +284,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -317,15 +318,9 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", - "@opentelemetry/api": "1.9.0", - "@opentelemetry/core": "2.6.1", - "@opentelemetry/resources": "2.6.1", - "@opentelemetry/sdk-trace-base": "2.6.1", - "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentelemetry/semantic-conventions": "1.40.0", "posthog-node": "4.4.0", }, "devDependencies": { @@ -337,10 +332,10 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@kobalte/core": "0.13.11", - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@pierre/diffs": "catalog:", "@solid-primitives/lifecycle": "0.1.2", "@solid-primitives/media": "2.3.3", @@ -372,7 +367,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-i18n": "workspace:*", @@ -433,7 +428,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.2.30", + "version": "7.2.34", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -560,7 +555,6 @@ "@octokit/webhooks-types": "7.6.1", "@opencode-ai/core": "workspace:*", "@opencode-ai/script": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", @@ -593,7 +587,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -618,7 +612,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "semver": "^7.6.3", }, @@ -629,7 +623,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "cross-spawn": "catalog:", }, @@ -642,33 +636,9 @@ "typescript": "catalog:", }, }, - "packages/shared": { - "name": "@opencode-ai/shared", - "version": "7.2.30", - "bin": { - "opencode": "./bin/opencode", - }, - "dependencies": { - "@effect/platform-node": "catalog:", - "@npmcli/arborist": "catalog:", - "effect": "catalog:", - "glob": "13.0.5", - "mime-types": "3.0.2", - "minimatch": "10.2.5", - "semver": "catalog:", - "xdg-basedir": "5.1.0", - "zod": "catalog:", - }, - "devDependencies": { - "@tsconfig/bun": "catalog:", - "@types/bun": "catalog:", - "@types/npmcli__arborist": "6.3.3", - "@types/semver": "catalog:", - }, - }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.2.30", + "version": "7.2.34", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -691,12 +661,11 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.2.30", + "version": "7.2.34", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", @@ -1678,8 +1647,6 @@ "@opencode-ai/sdk": ["@opencode-ai/sdk@1.4.12", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-V8/RnAPOcvvEOKefNBuWsrd1rY23bJUYVeI2CS1CEW9bUf0Q9JNESBT7Eop6jv77VBAwzljZju54AzgsCB7ydw=="], - "@opencode-ai/shared": ["@opencode-ai/shared@workspace:packages/shared"], - "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"], "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], diff --git a/nix/hashes.json b/nix/hashes.json index c92c084061b..7e5e025b455 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-cAQ3LYGSSUAxDH77qouZcG4dPjTBIysPAkLwXHkRza4=", - "aarch64-linux": "sha256-49haI3uiwlTn4YYJsCyEEMTXmjGYyktm8qC5zQ0MAzg=", - "aarch64-darwin": "sha256-sdd37hufZNCHQuVkDxaHc6xVqovz/BuCPbBOoDbcuMQ=", - "x86_64-darwin": "sha256-Ndl68EHo83z9C3WFDNqRDni6fdKSX9nj4/5o+3wxzNs=" + "x86_64-linux": "sha256-4hLG583KyBLxAOYvIiVMh+xLtVAdR3QgWFEqnGlZLZw=", + "aarch64-linux": "sha256-+60DxQ/GKAAoMFuAQRFoYM0HlZMzpLpLrTD7qmweUUA=", + "aarch64-darwin": "sha256-3WCWVqaxu+lCIc51N7wOttM/hoXbbnmtw7EP2eGstQg=", + "x86_64-darwin": "sha256-K8iG+OHkgC/lwud6hQ91sXF3c7UbdQQYRU/KK2P+B/4=" } } diff --git a/package.json b/package.json index 96f94bfef44..dfb7e705dcb 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "devDependencies": { "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", "glob": "13.0.5", @@ -148,6 +149,6 @@ "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "stream-chat@9.38.0": "patches/stream-chat@9.38.0.patch" }, - "version": "7.2.30", + "version": "7.2.34", "peerDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 6b9e055fa91..36fe9098408 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "7.2.30", + "version": "7.2.34", "description": "", "type": "module", "exports": { @@ -42,7 +42,6 @@ "@kobalte/core": "catalog:", "@kilocode/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", diff --git a/packages/app/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts deleted file mode 120000 index e4ea0d6cebd..00000000000 --- a/packages/app/src/custom-elements.d.ts +++ /dev/null @@ -1 +0,0 @@ -../../ui/src/custom-elements.d.ts \ No newline at end of file diff --git a/packages/app/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts new file mode 100644 index 00000000000..ce6acfabc34 --- /dev/null +++ b/packages/app/src/custom-elements.d.ts @@ -0,0 +1,3 @@ +// kilocode_change start - keep Windows checkouts parseable when symlinks are disabled. +import "../../ui/src/custom-elements" +// kilocode_change end diff --git a/packages/core/package.json b/packages/core/package.json index 7204398d39b..e0c5c727f1a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,6 +40,7 @@ "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", + "rotating-file-stream": "3.2.9", "semver": "^7.6.3", "xdg-basedir": "5.1.0", "zod": "catalog:" diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index 92731bb63c7..c084423813b 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -7,15 +7,14 @@ import { Flock } from "./util/flock" const app = "kilo" // kilocode_change // kilocode_change start -// Defensively strip surrounding whitespace from the resolved XDG paths. +// Defensively strip newline characters from the resolved XDG paths. // If `$HOME` (or any `$XDG_*_HOME` override) has a trailing newline in // the user's shell — e.g. because a shell snippet did `export HOME=$(cmd)` // against a command with an implicit newline — the unsanitised path // makes `fs.mkdir` try to create `/Users/\n` and fail with EACCES, // which breaks every `kilo` invocation at startup (including the SDK -// regen that runs during `bun run extension`). A trim is cheap and -// trailing whitespace is never legitimate in a filesystem path. -const clean = (p: string | undefined) => p?.trim() +// regen that runs during `bun run extension`). +const clean = (p: string | undefined) => p?.replace(/[\r\n]+/g, "") const data = path.join(clean(xdgData)!, app) const cache = path.join(clean(xdgCache)!, app) const config = path.join(clean(xdgConfig)!, app) diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index bde4ac24ed5..d5f38dd9932 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "7.2.30", + "version": "7.2.34", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 05310cc3de3..b98c61dbe34 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "7.2.30", + "version": "7.2.34", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 81db7f87b5c..49a7ad2f872 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "1.14.29" +version = "7.2.34" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.29/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.34/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.29/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.34/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.29/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.34/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.29/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.34/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.29/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.34/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/lib/nav/ai-providers.ts b/packages/kilo-docs/lib/nav/ai-providers.ts index bb9f109ae75..408598f0f52 100644 --- a/packages/kilo-docs/lib/nav/ai-providers.ts +++ b/packages/kilo-docs/lib/nav/ai-providers.ts @@ -31,6 +31,7 @@ export const AiProvidersNav: NavSection[] = [ { href: "/ai-providers/glama", children: "Glama" }, { href: "/ai-providers/requesty", children: "Requesty" }, { href: "/ai-providers/unbound", children: "Unbound" }, + { href: "/ai-providers/zenmux", children: "ZenMux" }, { href: "/ai-providers/vercel-ai-gateway", children: "Vercel AI Gateway", @@ -42,6 +43,8 @@ export const AiProvidersNav: NavSection[] = [ links: [ { href: "/ai-providers/vertex", children: "Google Vertex AI" }, { href: "/ai-providers/bedrock", children: "AWS Bedrock" }, + { href: "/ai-providers/alibaba", children: "Alibaba Cloud" }, + { href: "/ai-providers/cloudflare", children: "Cloudflare" }, { href: "/ai-providers/groq", children: "Groq" }, { href: "/ai-providers/cerebras", children: "Cerebras" }, { href: "/ai-providers/fireworks", children: "Fireworks AI" }, @@ -68,6 +71,7 @@ export const AiProvidersNav: NavSection[] = [ { href: "/ai-providers/moonshot", children: "Moonshot" }, { href: "/ai-providers/ovhcloud", children: "OVHcloud" }, { href: "/ai-providers/sap-ai-core", children: "SAP AI Core" }, + { href: "/ai-providers/venice", children: "Venice AI" }, ], }, { diff --git a/packages/kilo-docs/lib/nav/contributing.ts b/packages/kilo-docs/lib/nav/contributing.ts index 74e75c5ede6..14f4dae5e63 100644 --- a/packages/kilo-docs/lib/nav/contributing.ts +++ b/packages/kilo-docs/lib/nav/contributing.ts @@ -70,6 +70,10 @@ export const ContributingNav: NavSection[] = [ href: "/contributing/architecture/voice-transcription", children: "Voice Transcription", }, + { + href: "/contributing/architecture/per-message-feedback", + children: "Per-Message Feedback", + }, ], }, ], diff --git a/packages/kilo-docs/lib/nav/customize.ts b/packages/kilo-docs/lib/nav/customize.ts index 7687bad119f..83006b9e1bd 100644 --- a/packages/kilo-docs/lib/nav/customize.ts +++ b/packages/kilo-docs/lib/nav/customize.ts @@ -37,7 +37,6 @@ export const CustomizeNav: NavSection[] = [ { href: "/customize/context/codebase-indexing", children: "Codebase Indexing", - platform: "legacy", }, { href: "/customize/context/context-condensing", diff --git a/packages/kilo-docs/lib/nav/kiloclaw.ts b/packages/kilo-docs/lib/nav/kiloclaw.ts index 5b232a18f4e..5d051fb7255 100644 --- a/packages/kilo-docs/lib/nav/kiloclaw.ts +++ b/packages/kilo-docs/lib/nav/kiloclaw.ts @@ -54,6 +54,7 @@ export const KiloClawNav: NavSection[] = [ { href: "/kiloclaw/tools/1password", children: "1Password" }, { href: "/kiloclaw/tools/brave-search", children: "Brave Search" }, { href: "/kiloclaw/tools/agentcard", children: "AgentCard" }, + { href: "/kiloclaw/tools/other-tools", children: "Other Tools" }, ], }, { diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 10097f5e6a2..fb49ffb0366 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.2.30", + "version": "7.2.34", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-docs/pages/ai-providers/alibaba.md b/packages/kilo-docs/pages/ai-providers/alibaba.md new file mode 100644 index 00000000000..63ac7038406 --- /dev/null +++ b/packages/kilo-docs/pages/ai-providers/alibaba.md @@ -0,0 +1,79 @@ +--- +sidebar_label: Alibaba Cloud +--- + +# Using Alibaba Cloud With Kilo Code + +Kilo Code supports Alibaba Cloud Model Studio (DashScope) through the native Alibaba AI SDK provider. Use it to run Qwen and other DashScope-hosted models directly from Kilo. + +**Website:** [https://www.alibabacloud.com/product/modelstudio](https://www.alibabacloud.com/product/modelstudio) + +## Getting an API Key + +1. Sign in to Alibaba Cloud Model Studio or DashScope. +2. Open the API key section for your workspace. +3. Create a key and copy it immediately. +4. Store the key securely; do not commit it to your repository. + +## Configuration in Kilo Code + +{% tabs %} +{% tab label="VSCode (Legacy)" %} + +Use the **OpenAI Compatible** provider if the legacy provider list does not include Alibaba Cloud. Set the base URL and API key from your DashScope account, then choose or enter a supported model ID. + +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Alibaba Cloud. Enter your DashScope API key and choose a supported model from the model picker. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the DashScope API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export DASHSCOPE_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "alibaba": { + "env": ["DASHSCOPE_API_KEY"] + } + } +} +``` + +Then set your default model: + +```jsonc +{ + "model": "alibaba/qwen-plus" +} +``` + +Some regions and model catalogs may expose Alibaba models under `alibaba-cn` instead. In that case, use the same API key and set the model as `alibaba-cn/`. + +{% /tab %} +{% /tabs %} + +## Tips and Notes + +- **Native provider:** Kilo uses the native Alibaba AI SDK provider, not a generic compatibility shim, when you select the built-in Alibaba provider. +- **Cache controls:** Kilo forwards provider cache controls for compatible Alibaba/DashScope models, which can reduce repeated context cost where the model supports caching. +- **Regional catalogs:** Model availability varies by account, region, and gateway. Check the DashScope console for the exact model IDs available to your account. +- **Pricing and limits:** Refer to Alibaba Cloud Model Studio pricing and rate-limit documentation for current usage details. + +## Troubleshooting + +- **Authentication errors:** Verify `DASHSCOPE_API_KEY` is set in the same shell that launches Kilo, or stored in your Kilo provider config. +- **Model not found:** Confirm the model ID is available in your DashScope account and try the model picker to see Kilo's current catalog. +- **Region mismatch:** If your account uses the China-region catalog, try the `alibaba-cn/` provider prefix. diff --git a/packages/kilo-docs/pages/ai-providers/anthropic.md b/packages/kilo-docs/pages/ai-providers/anthropic.md index 767aed584cb..e5e75b486e0 100644 --- a/packages/kilo-docs/pages/ai-providers/anthropic.md +++ b/packages/kilo-docs/pages/ai-providers/anthropic.md @@ -1,4 +1,6 @@ --- +title: "Using Anthropic Claude with Kilo Code" +description: "Configure Anthropic's Claude models in Kilo Code. Guide to getting an API key, setting up Claude Sonnet and Opus in VS Code and the CLI." sidebar_label: Anthropic --- diff --git a/packages/kilo-docs/pages/ai-providers/bedrock.md b/packages/kilo-docs/pages/ai-providers/bedrock.md index 64f4bb7aea2..5b6388dc0ab 100644 --- a/packages/kilo-docs/pages/ai-providers/bedrock.md +++ b/packages/kilo-docs/pages/ai-providers/bedrock.md @@ -1,4 +1,6 @@ --- +title: "Using AWS Bedrock with Kilo Code" +description: "Configure AWS Bedrock in Kilo Code to access Claude, Llama, and other foundation models through your AWS account." sidebar_label: AWS Bedrock --- diff --git a/packages/kilo-docs/pages/ai-providers/cerebras.md b/packages/kilo-docs/pages/ai-providers/cerebras.md index 24c19bdc421..6ca2f432ed0 100644 --- a/packages/kilo-docs/pages/ai-providers/cerebras.md +++ b/packages/kilo-docs/pages/ai-providers/cerebras.md @@ -1,4 +1,6 @@ --- +title: "Using Cerebras with Kilo Code | Ultra-Fast Inference" +description: "Run Llama and Qwen models at record-breaking speeds by configuring Cerebras in Kilo Code. Setup for VS Code and the CLI." sidebar_label: Cerebras --- diff --git a/packages/kilo-docs/pages/ai-providers/chutes-ai.md b/packages/kilo-docs/pages/ai-providers/chutes-ai.md index 348b3b84feb..e8b6ce70ab1 100644 --- a/packages/kilo-docs/pages/ai-providers/chutes-ai.md +++ b/packages/kilo-docs/pages/ai-providers/chutes-ai.md @@ -1,4 +1,6 @@ --- +title: "Using Chutes AI with Kilo Code" +description: "Access open-source AI models through Chutes AI in Kilo Code. Setup guide for getting an API key and configuring models." sidebar_label: Chutes AI --- diff --git a/packages/kilo-docs/pages/ai-providers/claude-code.md b/packages/kilo-docs/pages/ai-providers/claude-code.md index 239e3d779ca..f7a6f639018 100644 --- a/packages/kilo-docs/pages/ai-providers/claude-code.md +++ b/packages/kilo-docs/pages/ai-providers/claude-code.md @@ -1,4 +1,6 @@ --- +title: "Using Claude Code with Kilo Code" +description: "Use your Claude Code subscription with Kilo Code. Setup guide for authenticating and selecting Claude models." sidebar_label: Claude Code --- diff --git a/packages/kilo-docs/pages/ai-providers/cloudflare.md b/packages/kilo-docs/pages/ai-providers/cloudflare.md new file mode 100644 index 00000000000..c7bb94a236e --- /dev/null +++ b/packages/kilo-docs/pages/ai-providers/cloudflare.md @@ -0,0 +1,128 @@ +--- +sidebar_label: Cloudflare +--- + +# Using Cloudflare With Kilo Code + +Kilo Code supports both Cloudflare Workers AI and Cloudflare AI Gateway. Workers AI runs Cloudflare-hosted models directly, while AI Gateway routes requests through your Cloudflare gateway to one or more upstream providers. + +**Website:** [https://developers.cloudflare.com/ai/](https://developers.cloudflare.com/ai/) + +## Provider Options + +| Provider ID | Use it for | Required values | +|---|---|---| +| `cloudflare-workers-ai` | Cloudflare-hosted Workers AI models | Account ID and API key | +| `cloudflare-ai-gateway` | Routing through Cloudflare AI Gateway | Account ID, Gateway ID, and Gateway API token | + +You can enter these values interactively with `/connect` in the TUI or `kilo auth` from the CLI, or provide them through environment variables. + +## Cloudflare Workers AI + +### Get Credentials + +1. Open the Cloudflare dashboard and select your account. +2. Copy the **Account ID** from the dashboard. +3. Create an API token that can invoke Workers AI. + +### Configure Kilo + +{% tabs %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Cloudflare Workers AI. If your account ID is not already available from the environment, Kilo prompts for it while connecting the provider. + +{% /tab %} +{% tab label="CLI" %} + +Use `/connect` in the TUI and choose Cloudflare Workers AI, or run: + +```bash +kilo auth cloudflare-workers-ai +``` + +Alternatively, set environment variables before launching Kilo: + +```bash +export CLOUDFLARE_ACCOUNT_ID="your-account-id" +export CLOUDFLARE_API_KEY="your-workers-ai-api-key" +``` + +Then choose a Workers AI model from the model picker, or set a default model such as: + +```jsonc +{ + "model": "cloudflare-workers-ai/@cf/meta/llama-3.1-8b-instruct" +} +``` + +{% /tab %} +{% /tabs %} + +## Cloudflare AI Gateway + +### Get Credentials + +1. Create or open a Cloudflare AI Gateway. +2. Copy your **Account ID** and **Gateway ID**. +3. Create a Gateway API token. + +### Configure Kilo + +{% tabs %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Cloudflare AI Gateway. Kilo prompts for the account ID and gateway ID when they are not already set in the environment. + +{% /tab %} +{% tab label="CLI" %} + +Use `/connect` in the TUI and choose Cloudflare AI Gateway, or run: + +```bash +kilo auth cloudflare-ai-gateway +``` + +Alternatively, set environment variables before launching Kilo: + +```bash +export CLOUDFLARE_ACCOUNT_ID="your-account-id" +export CLOUDFLARE_GATEWAY_ID="your-gateway-id" +export CLOUDFLARE_API_TOKEN="your-gateway-api-token" +``` + +`CF_AIG_TOKEN` is also accepted as an alternative to `CLOUDFLARE_API_TOKEN`. + +Then choose a gateway-backed model from the model picker, or set a default model such as: + +```jsonc +{ + "model": "cloudflare-ai-gateway/openai/gpt-5.1" +} +``` + +{% /tab %} +{% /tabs %} + +## Advanced Configuration + +If your organization provides a fully configured gateway URL, set `provider..options.baseURL`. When `baseURL` is configured, Kilo skips the built-in Account ID and Gateway ID checks because the URL already identifies the target service. + +```jsonc +{ + "provider": { + "cloudflare-ai-gateway": { + "options": { + "baseURL": "https://gateway.example.com/v1" + } + } + } +} +``` + +## Troubleshooting + +- **`CLOUDFLARE_ACCOUNT_ID is missing`** — set `CLOUDFLARE_ACCOUNT_ID` or reconnect the provider with `/connect` so Kilo can store the account ID in auth metadata. +- **`CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_GATEWAY_ID missing`** — set both values or run `kilo auth cloudflare-ai-gateway`. +- **`CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required`** — set one of those variables or reconnect Cloudflare AI Gateway with `kilo auth cloudflare-ai-gateway`. +- **Model errors through AI Gateway** — confirm the gateway route supports the selected upstream provider/model and that your Gateway API token has access. diff --git a/packages/kilo-docs/pages/ai-providers/deepseek.md b/packages/kilo-docs/pages/ai-providers/deepseek.md index d70c070f3e8..8323bc9d0c1 100644 --- a/packages/kilo-docs/pages/ai-providers/deepseek.md +++ b/packages/kilo-docs/pages/ai-providers/deepseek.md @@ -1,4 +1,6 @@ --- +title: "Using DeepSeek with Kilo Code" +description: "Connect DeepSeek's reasoning and coding models to Kilo Code. Setup guide for DeepSeek-V3 and DeepSeek-R1 in VS Code and the CLI." sidebar_label: DeepSeek --- diff --git a/packages/kilo-docs/pages/ai-providers/fireworks.md b/packages/kilo-docs/pages/ai-providers/fireworks.md index f354b533760..97db2ca21ff 100644 --- a/packages/kilo-docs/pages/ai-providers/fireworks.md +++ b/packages/kilo-docs/pages/ai-providers/fireworks.md @@ -1,5 +1,6 @@ --- -title: Fireworks AI with Kilo Code +title: "Using Fireworks AI with Kilo Code | Fast Inference" +description: "Run open-source and proprietary models on Fireworks AI's high-performance platform in Kilo Code. Setup guide for VS Code and the CLI." --- # Using Fireworks AI With Kilo Code diff --git a/packages/kilo-docs/pages/ai-providers/gemini.md b/packages/kilo-docs/pages/ai-providers/gemini.md index 3a0ffe09eec..775b9d244e5 100644 --- a/packages/kilo-docs/pages/ai-providers/gemini.md +++ b/packages/kilo-docs/pages/ai-providers/gemini.md @@ -1,4 +1,6 @@ --- +title: "Using Google Gemini with Kilo Code" +description: "Connect Google Gemini models to Kilo Code. Guide to getting an API key from Google AI Studio and configuring Gemini in VS Code and the CLI." sidebar_label: Google Gemini --- diff --git a/packages/kilo-docs/pages/ai-providers/glama.md b/packages/kilo-docs/pages/ai-providers/glama.md index fb000fc64bb..04a957b5d8d 100644 --- a/packages/kilo-docs/pages/ai-providers/glama.md +++ b/packages/kilo-docs/pages/ai-providers/glama.md @@ -1,4 +1,6 @@ --- +title: "Using Glama with Kilo Code" +description: "Connect Glama's unified AI gateway to Kilo Code to access Claude, GPT, and other leading models through a single API key." sidebar_label: Glama --- diff --git a/packages/kilo-docs/pages/ai-providers/groq.md b/packages/kilo-docs/pages/ai-providers/groq.md index 6e0ab33b4fc..9bbe353871d 100644 --- a/packages/kilo-docs/pages/ai-providers/groq.md +++ b/packages/kilo-docs/pages/ai-providers/groq.md @@ -1,4 +1,6 @@ --- +title: "Using Groq with Kilo Code | Fast LLM Inference" +description: "Run Llama, Mixtral, and other models at ultra-low latency by configuring Groq in Kilo Code. Setup guide for VS Code and the CLI." sidebar_label: Groq --- diff --git a/packages/kilo-docs/pages/ai-providers/human-relay.md b/packages/kilo-docs/pages/ai-providers/human-relay.md index f39befdbe9c..a0b3219310a 100644 --- a/packages/kilo-docs/pages/ai-providers/human-relay.md +++ b/packages/kilo-docs/pages/ai-providers/human-relay.md @@ -1,3 +1,9 @@ +--- +title: "Using Human Relay with Kilo Code | No API Key Needed" +description: "Use Kilo Code with web-based AI chats like ChatGPT and Claude without an API key by manually relaying messages via Human Relay." +sidebar_label: Human Relay +--- + # Human Relay Provider The Human Relay provider allows you to use Kilo Code with web-based AI models like ChatGPT or Claude without needing an API key. Instead, it relies on you to manually relay messages between Kilo Code and the AI's web interface. diff --git a/packages/kilo-docs/pages/ai-providers/inception.md b/packages/kilo-docs/pages/ai-providers/inception.md index 8431a37fc6f..1698531ff0c 100644 --- a/packages/kilo-docs/pages/ai-providers/inception.md +++ b/packages/kilo-docs/pages/ai-providers/inception.md @@ -1,4 +1,6 @@ --- +title: "Using Inception Labs with Kilo Code" +description: "Connect Inception Labs' Mercury diffusion LLMs to Kilo Code for ultra-fast code generation. Setup guide for VS Code and the CLI." sidebar_label: Inception --- diff --git a/packages/kilo-docs/pages/ai-providers/index.md b/packages/kilo-docs/pages/ai-providers/index.md index bc9822b1db1..a7c8c05fee3 100644 --- a/packages/kilo-docs/pages/ai-providers/index.md +++ b/packages/kilo-docs/pages/ai-providers/index.md @@ -22,6 +22,10 @@ Major AI companies offering powerful models via API: - **[Anthropic](/docs/ai-providers/anthropic)** - Claude models (Claude 4, Claude 3.5 Sonnet, etc.) - **[OpenAI](/docs/ai-providers/openai)** - GPT-4, GPT-4o, o1, and more - **[Google Gemini](/docs/ai-providers/gemini)** - Gemini Pro, Gemini Ultra +- **[Google Vertex AI](/docs/ai-providers/vertex)** - Google Cloud-hosted Gemini and partner models +- **[AWS Bedrock](/docs/ai-providers/bedrock)** - AWS-hosted foundation models +- **[Alibaba Cloud](/docs/ai-providers/alibaba)** - DashScope and Qwen models through Model Studio +- **[Cloudflare](/docs/ai-providers/cloudflare)** - Workers AI and Cloudflare AI Gateway - **[DeepSeek](/docs/ai-providers/deepseek)** - DeepSeek V3., R1 - **[Mistral](/docs/ai-providers/mistral)** - Mistral Large, Codestral @@ -40,6 +44,7 @@ Route requests through unified APIs with additional features: - **[OpenRouter](/docs/ai-providers/openrouter)** - Access multiple providers through one API - **[Glama](/docs/ai-providers/glama)** - Enterprise AI gateway - **[Requesty](/docs/ai-providers/requesty)** - Smart routing and fallbacks +- **[Cloudflare AI Gateway](/docs/ai-providers/cloudflare)** - Route providers through your Cloudflare account ## Choosing a Provider diff --git a/packages/kilo-docs/pages/ai-providers/kilocode.md b/packages/kilo-docs/pages/ai-providers/kilocode.md index 706ac4a5f63..25b1341b7dd 100644 --- a/packages/kilo-docs/pages/ai-providers/kilocode.md +++ b/packages/kilo-docs/pages/ai-providers/kilocode.md @@ -1,4 +1,6 @@ --- +title: "Using the Kilo Code Provider" +description: "The built-in Kilo Code provider gives you access to top AI models with one account. Setup and sign-in guide." sidebar_label: Kilo Code Provider --- diff --git a/packages/kilo-docs/pages/ai-providers/lmstudio.md b/packages/kilo-docs/pages/ai-providers/lmstudio.md index f9aaf02832c..aca742cdd27 100644 --- a/packages/kilo-docs/pages/ai-providers/lmstudio.md +++ b/packages/kilo-docs/pages/ai-providers/lmstudio.md @@ -1,4 +1,6 @@ --- +title: "Using LM Studio with Kilo Code | Local LLMs" +description: "Run local LLMs in Kilo Code by connecting to LM Studio's OpenAI-compatible server. Setup guide for VS Code and the CLI." sidebar_label: LM Studio --- diff --git a/packages/kilo-docs/pages/ai-providers/minimax.md b/packages/kilo-docs/pages/ai-providers/minimax.md index 4f36072519f..4641f5d136b 100644 --- a/packages/kilo-docs/pages/ai-providers/minimax.md +++ b/packages/kilo-docs/pages/ai-providers/minimax.md @@ -1,4 +1,6 @@ --- +title: "Using MiniMax with Kilo Code" +description: "Configure MiniMax AI models in Kilo Code. Guide to getting an API key and setup for VS Code and the CLI." sidebar_label: MiniMax --- diff --git a/packages/kilo-docs/pages/ai-providers/mistral.md b/packages/kilo-docs/pages/ai-providers/mistral.md index e1ce39948b2..684191febc1 100644 --- a/packages/kilo-docs/pages/ai-providers/mistral.md +++ b/packages/kilo-docs/pages/ai-providers/mistral.md @@ -1,4 +1,6 @@ --- +title: "Using Mistral AI with Kilo Code" +description: "Configure Mistral AI models, including Codestral, in Kilo Code. Guide to getting an API key and setup for VS Code and the CLI." sidebar_label: Mistral AI --- @@ -66,6 +68,12 @@ Then set your default model: {% /tab %} {% /tabs %} +## Reasoning Variants + +Mistral's adjustable reasoning support is exposed only for reasoning-capable Mistral Small 4 models: `mistral-small-2603` and `mistral-small-latest`. When one of these models is selected, Kilo offers a `high` variant that sends `reasoningEffort: "high"` to the Mistral provider. + +Other Mistral models do not get automatic reasoning variants, even if they appear in the same provider. See Mistral's [adjustable reasoning documentation](https://docs.mistral.ai/capabilities/reasoning/adjustable) for provider-level details. + ## Using Codestral [Codestral](https://docs.mistral.ai/capabilities/code_generation/) is a model specifically designed for code generation and interaction. diff --git a/packages/kilo-docs/pages/ai-providers/moonshot.md b/packages/kilo-docs/pages/ai-providers/moonshot.md index 7b92cf10287..0becc0b52dd 100644 --- a/packages/kilo-docs/pages/ai-providers/moonshot.md +++ b/packages/kilo-docs/pages/ai-providers/moonshot.md @@ -1,4 +1,6 @@ --- +title: "Using Moonshot AI (Kimi) with Kilo Code" +description: "Connect Moonshot's Kimi models to Kilo Code. Setup guide for getting an API key and selecting models in VS Code and the CLI." sidebar_label: Moonshot.ai --- @@ -70,5 +72,6 @@ Then set your default model: - **Ultra-Long Context:** Kimi models excel at handling large codebases and complex projects with their extended context windows. - **Reasoning Capabilities:** The K2-Thinking variant provides enhanced problem-solving through extended reasoning chains. +- **Kimi-specific prompting:** Kilo automatically uses a Kimi-tuned system prompt for model IDs containing `kimi`; no extra configuration is required. - **Language Support:** Kimi models have strong support for both English and Chinese languages. - **Pricing:** Refer to the Moonshot.ai platform for current pricing information on different models. diff --git a/packages/kilo-docs/pages/ai-providers/ollama.md b/packages/kilo-docs/pages/ai-providers/ollama.md index 3f2ba1887c2..a2782d9c451 100644 --- a/packages/kilo-docs/pages/ai-providers/ollama.md +++ b/packages/kilo-docs/pages/ai-providers/ollama.md @@ -1,4 +1,6 @@ --- +title: "Using Ollama with Kilo Code | Run Local Models" +description: "Run local AI models with Ollama in Kilo Code for offline, private coding. Setup guide for VS Code and the CLI." sidebar_label: Ollama --- diff --git a/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md b/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md index 36c6b3c0551..5331c364476 100644 --- a/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md +++ b/packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md @@ -1,4 +1,6 @@ --- +title: "Using ChatGPT Plus/Pro with Kilo Code" +description: "Use your ChatGPT Plus or Pro subscription with Kilo Code. Setup guide for authenticating without a separate API key." sidebar_label: ChatGPT Plus/Pro --- diff --git a/packages/kilo-docs/pages/ai-providers/openai-compatible.md b/packages/kilo-docs/pages/ai-providers/openai-compatible.md index d8999c3d3b6..89551016dcd 100644 --- a/packages/kilo-docs/pages/ai-providers/openai-compatible.md +++ b/packages/kilo-docs/pages/ai-providers/openai-compatible.md @@ -1,4 +1,6 @@ --- +title: "Using OpenAI-Compatible Providers with Kilo Code" +description: "Connect any OpenAI-compatible API endpoint to Kilo Code, including Azure OpenAI, Together AI, and self-hosted servers." sidebar_label: OpenAI Compatible --- diff --git a/packages/kilo-docs/pages/ai-providers/openai.md b/packages/kilo-docs/pages/ai-providers/openai.md index 4c5a379b539..f100680ccfa 100644 --- a/packages/kilo-docs/pages/ai-providers/openai.md +++ b/packages/kilo-docs/pages/ai-providers/openai.md @@ -1,4 +1,6 @@ --- +title: "Using OpenAI with Kilo Code | Setup & Models" +description: "Connect the official OpenAI API to Kilo Code. Step-by-step guide to creating an API key and configuring GPT models in VS Code and the CLI." sidebar_label: OpenAI --- diff --git a/packages/kilo-docs/pages/ai-providers/openrouter.md b/packages/kilo-docs/pages/ai-providers/openrouter.md index bdcbbf1a528..1750b8699b5 100644 --- a/packages/kilo-docs/pages/ai-providers/openrouter.md +++ b/packages/kilo-docs/pages/ai-providers/openrouter.md @@ -1,4 +1,6 @@ --- +title: "Using OpenRouter with Kilo Code | Unified AI API" +description: "Access hundreds of AI models through a single API by configuring OpenRouter in Kilo Code. Setup guide for VS Code and the CLI." sidebar_label: OpenRouter --- @@ -17,15 +19,6 @@ OpenRouter is an AI platform that provides access to a wide variety of language ## Configuration in Kilo Code {% tabs %} -{% tab label="VSCode (Legacy)" %} - -1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. -2. **Select Provider:** Choose "OpenRouter" from the "API Provider" dropdown. -3. **Enter API Key:** Paste your OpenRouter API key into the "OpenRouter API Key" field. -4. **Select Model:** Choose your desired model from the "Model" dropdown. -5. **(Optional) Custom Base URL:** If you need to use a custom base URL for the OpenRouter API, check "Use custom base URL" and enter the URL. Leave this blank for most users. - -{% /tab %} {% tab label="VSCode" %} Open **Settings** (gear icon) and go to the **Providers** tab to add OpenRouter and enter your API key. @@ -63,18 +56,90 @@ Then set your default model: } ``` +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. +2. **Select Provider:** Choose "OpenRouter" from the "API Provider" dropdown. +3. **Enter API Key:** Paste your OpenRouter API key into the "OpenRouter API Key" field. +4. **Select Model:** Choose your desired model from the "Model" dropdown. +5. **(Optional) Custom Base URL:** If you need to use a custom base URL for the OpenRouter API, check "Use custom base URL" and enter the URL. Leave this blank for most users. + {% /tab %} {% /tabs %} ## Supported Transforms -OpenRouter provides an [optional "middle-out" message transform](https://openrouter.ai/docs/features/message-transforms) to help with prompts that exceed the maximum context size of a model. You can enable it by checking the "Compress prompts and message chains to the context size" box. +OpenRouter provides an [optional "middle-out" message transform](https://openrouter.ai/docs/features/message-transforms) to help with prompts that exceed the maximum context size of a model. + +{% tabs %} +{% tab label="VSCode & CLI" %} + +The middle-out transform is not exposed as a dedicated UI control in the VS Code extension. To enable it, set `transforms` on the model's `options` in your `kilo.json` config file. Anything under `options` is forwarded verbatim to the OpenRouter AI SDK as `providerOptions.openrouter`: + +```jsonc +{ + "provider": { + "openrouter": { + "models": { + "anthropic/claude-sonnet-4-20250514": { + "options": { + "transforms": ["middle-out"], + }, + }, + }, + }, + }, +} +``` + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +The legacy extension does not expose a control for the middle-out transform, and it does not read configuration from `kilo.json`. To use this option, switch to the new VS Code extension or the CLI. + +{% /tab %} +{% /tabs %} ## Provider Routing -OpenRouter can route to many different inference providers and this can be controlled in the API Provider settings under Provider Routing. +OpenRouter can route to many different inference providers. This can be controlled directly via OpenRouter's [`provider` routing parameter](https://openrouter.ai/docs/features/provider-routing). -### Provider Sorting +{% tabs %} +{% tab label="VSCode & CLI" %} + +Provider routing is not exposed as dedicated UI controls in the VS Code extension. To configure it, set OpenRouter's `provider` routing fields under the model's `options` in your `kilo.json` config file. Everything under `options` is forwarded to the OpenRouter AI SDK as `providerOptions.openrouter`, so any field from the [OpenRouter provider routing docs](https://openrouter.ai/docs/features/provider-routing) can be used. + +```jsonc +{ + "provider": { + "openrouter": { + "models": { + "anthropic/claude-sonnet-4-20250514": { + "options": { + "provider": { + "sort": "price", // "price" | "throughput" | "latency" + "order": ["Anthropic", "Google"], // specific provider preference + "only": ["Anthropic"], // restrict to listed providers + "data_collection": "deny", // "allow" | "deny" + "zdr": true, // zero data retention + }, + }, + }, + }, + }, + }, +} +``` + +Omit any field to fall back to your OpenRouter account's default. Fields are passed through without validation — see OpenRouter's docs for the full list of supported values. + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +The legacy settings UI exposes Provider Routing as two dropdowns under **Provider Routing**: + +**Provider Sorting** - Default provider sorting: use the setting in your OpenRouter account - Prefer providers with lower price @@ -82,13 +147,16 @@ OpenRouter can route to many different inference providers and this can be contr - Prefer providers with lower latency (i.e. shorter time to first token) - A specific provider preference can also be chosen. -### Data Policy +**Data Policy** - No data policy set: use the settings in your OpenRouter account. - Allow prompt training: providers that may train on your prompts or completions are allowed. Free models generally require this option to be enabled. - Deny prompt training: providers that may train on your prompts or completions are not allowed. - Zero data retention: only providers with a strict zero data retention policy are allowed. This option is not recommended, as it will disable many popular providers, such as Anthropic and OpenAI. +{% /tab %} +{% /tabs %} + ## Tips and Notes - **Model Selection:** OpenRouter offers a wide range of models. Experiment to find the best one for your needs. diff --git a/packages/kilo-docs/pages/ai-providers/ovhcloud.md b/packages/kilo-docs/pages/ai-providers/ovhcloud.md index 7b9890a2598..9e1a79b72bc 100644 --- a/packages/kilo-docs/pages/ai-providers/ovhcloud.md +++ b/packages/kilo-docs/pages/ai-providers/ovhcloud.md @@ -1,4 +1,6 @@ --- +title: "Using OVHcloud AI Endpoints with Kilo Code" +description: "Connect OVHcloud's European AI Endpoints to Kilo Code for sovereign, GDPR-compliant model access. Setup guide for VS Code and the CLI." sidebar_label: OVHcloud AI Endpoints --- diff --git a/packages/kilo-docs/pages/ai-providers/requesty.md b/packages/kilo-docs/pages/ai-providers/requesty.md index fda3c5796e3..7b35d99901d 100644 --- a/packages/kilo-docs/pages/ai-providers/requesty.md +++ b/packages/kilo-docs/pages/ai-providers/requesty.md @@ -1,4 +1,6 @@ --- +title: "Using Requesty with Kilo Code" +description: "Route AI model requests through Requesty in Kilo Code for cost optimization and access to multiple providers from one API key." sidebar_label: Requesty --- diff --git a/packages/kilo-docs/pages/ai-providers/sap-ai-core.md b/packages/kilo-docs/pages/ai-providers/sap-ai-core.md index 16dfec8bbdf..655148fe95c 100644 --- a/packages/kilo-docs/pages/ai-providers/sap-ai-core.md +++ b/packages/kilo-docs/pages/ai-providers/sap-ai-core.md @@ -1,4 +1,6 @@ --- +title: "Using SAP AI Core with Kilo Code" +description: "Connect SAP AI Core to Kilo Code to use enterprise foundation models from your SAP BTP account. Setup and authentication guide." sidebar_label: SAP AI Core --- diff --git a/packages/kilo-docs/pages/ai-providers/synthetic.md b/packages/kilo-docs/pages/ai-providers/synthetic.md index 853f3a68b63..96051ba442f 100644 --- a/packages/kilo-docs/pages/ai-providers/synthetic.md +++ b/packages/kilo-docs/pages/ai-providers/synthetic.md @@ -1,4 +1,6 @@ --- +title: "Using Synthetic with Kilo Code" +description: "Access open-source AI models through Synthetic in Kilo Code. Setup guide for getting an API key and configuring models." sidebar_label: Synthetic --- diff --git a/packages/kilo-docs/pages/ai-providers/unbound.md b/packages/kilo-docs/pages/ai-providers/unbound.md index fd7f6fb6e27..055bd113951 100644 --- a/packages/kilo-docs/pages/ai-providers/unbound.md +++ b/packages/kilo-docs/pages/ai-providers/unbound.md @@ -1,4 +1,6 @@ --- +title: "Using Unbound with Kilo Code" +description: "Access Claude, GPT, and other leading models through the Unbound proxy in Kilo Code. Setup guide for VS Code and the CLI." sidebar_label: Unbound --- diff --git a/packages/kilo-docs/pages/ai-providers/v0.md b/packages/kilo-docs/pages/ai-providers/v0.md index 2ba2ba4ac67..bd1d1b16c37 100644 --- a/packages/kilo-docs/pages/ai-providers/v0.md +++ b/packages/kilo-docs/pages/ai-providers/v0.md @@ -1,4 +1,6 @@ --- +title: "Using Vercel v0 with Kilo Code" +description: "Connect Vercel's v0 models to Kilo Code for UI-focused code generation. Setup guide for VS Code and the CLI." sidebar_label: v0 --- diff --git a/packages/kilo-docs/pages/ai-providers/venice.md b/packages/kilo-docs/pages/ai-providers/venice.md new file mode 100644 index 00000000000..2c45bb6032b --- /dev/null +++ b/packages/kilo-docs/pages/ai-providers/venice.md @@ -0,0 +1,76 @@ +--- +sidebar_label: Venice AI +--- + +# Using Venice AI With Kilo Code + +Kilo Code supports Venice AI through the native Venice provider. Venice offers privacy-focused access to open and reasoning-capable models through its API. + +**Website:** [https://venice.ai/](https://venice.ai/) + +## Getting an API Key + +1. Sign in to Venice AI. +2. Open your API settings. +3. Create a key for Kilo Code. +4. Copy the key immediately and store it securely. + +## Configuration in Kilo Code + +{% tabs %} +{% tab label="VSCode (Legacy)" %} + +Use the **OpenAI Compatible** provider if the legacy provider list does not include Venice AI. Enter your Venice API base URL, API key, and model ID from the Venice dashboard. + +{% /tab %} +{% tab label="VSCode" %} + +Open **Settings** (gear icon) and go to the **Providers** tab to add Venice AI and enter your API key. + +The extension stores this in your `kilo.json` config file. You can also edit the config file directly — see the **CLI** tab for the file format. + +{% /tab %} +{% tab label="CLI" %} + +Set the API key as an environment variable or configure it in your `kilo.json` config file: + +**Environment variable:** + +```bash +export VENICE_API_KEY="your-api-key" +``` + +**Config file** (`~/.config/kilo/kilo.json` or `./kilo.json`): + +```jsonc +{ + "provider": { + "venice": { + "env": ["VENICE_API_KEY"] + } + } +} +``` + +Then select a Venice model from the model picker, or set a default model after confirming the model ID in your account: + +```jsonc +{ + "model": "venice/" +} +``` + +{% /tab %} +{% /tabs %} + +## Tips and Notes + +- **Native provider:** Kilo uses the Venice AI SDK provider for built-in Venice models. +- **Reasoning models:** Some Venice models expose reasoning controls. Kilo maps supported reasoning effort and disable-thinking options when the selected model supports them. +- **Model availability:** Venice model IDs and access can vary by plan. Use the model picker or Venice dashboard for the current list. +- **Pricing:** Refer to Venice AI pricing and account limits for current token costs and rate limits. + +## Troubleshooting + +- **Invalid API key:** Verify `VENICE_API_KEY` is set in the same environment that launches Kilo, or reconnect the provider in Settings. +- **Model not available:** Confirm your Venice account has access to the selected model and try selecting it from the model picker. diff --git a/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md b/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md index bbfd9ccf991..58eee777d5e 100644 --- a/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md +++ b/packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md @@ -1,4 +1,5 @@ --- +title: "Using Vercel AI Gateway with Kilo Code" description: Configure the Vercel AI Gateway in Kilo Code to robustly access 100+ language models from various providers through a centralized interface. keywords: - kilo code diff --git a/packages/kilo-docs/pages/ai-providers/vertex.md b/packages/kilo-docs/pages/ai-providers/vertex.md index abbb56da3b8..97eb603eaae 100644 --- a/packages/kilo-docs/pages/ai-providers/vertex.md +++ b/packages/kilo-docs/pages/ai-providers/vertex.md @@ -1,4 +1,6 @@ --- +title: "Using GCP Vertex AI with Kilo Code" +description: "Connect Google Cloud Vertex AI to Kilo Code to use Claude, Gemini, and other models through your GCP account." sidebar_label: GCP Vertex AI --- @@ -81,4 +83,5 @@ Then set your default model: ## Tips and Notes - **Permissions:** Ensure your Google Cloud account has the necessary permissions to access Vertex AI and the specific models you want to use. +- **Prompt caching:** Claude models served through Vertex AI support Kilo prompt caching. Kilo applies Anthropic cache controls and tracks cache write/read tokens when Vertex reports them. Native Vertex Gemini models use Google's implicit server-side caching; no extra Kilo configuration is required, and Gemini may not report cache write tokens. - **Pricing:** Refer to the [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) page for details. diff --git a/packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md b/packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md index e590eedbb93..fe6b6bd809c 100644 --- a/packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md +++ b/packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md @@ -1,4 +1,6 @@ --- +title: "Virtual Quota Fallback in Kilo Code" +description: "Configure automatic provider fallback in Kilo Code to handle quota limits and keep your AI coding workflow uninterrupted." sidebar_label: Virtual Quota Fallback --- diff --git a/packages/kilo-docs/pages/ai-providers/vscode-lm.md b/packages/kilo-docs/pages/ai-providers/vscode-lm.md index df8e16e1bd0..60ad4fbe7b1 100644 --- a/packages/kilo-docs/pages/ai-providers/vscode-lm.md +++ b/packages/kilo-docs/pages/ai-providers/vscode-lm.md @@ -1,4 +1,6 @@ --- +title: "Using the VS Code Language Model API with Kilo Code" +description: "Use GitHub Copilot's models in Kilo Code via the VS Code Language Model API. Setup guide and requirements." sidebar_label: VS Code Language Model API --- diff --git a/packages/kilo-docs/pages/ai-providers/xai.md b/packages/kilo-docs/pages/ai-providers/xai.md index 5435c6c7ecd..dc74332d781 100644 --- a/packages/kilo-docs/pages/ai-providers/xai.md +++ b/packages/kilo-docs/pages/ai-providers/xai.md @@ -1,4 +1,6 @@ --- +title: "Using xAI Grok with Kilo Code" +description: "Connect xAI's Grok models to Kilo Code. Guide to getting an API key and configuring Grok in VS Code and the CLI." sidebar_label: xAI (Grok) --- diff --git a/packages/kilo-docs/pages/ai-providers/zenmux.md b/packages/kilo-docs/pages/ai-providers/zenmux.md index de7985830eb..03034211ee4 100644 --- a/packages/kilo-docs/pages/ai-providers/zenmux.md +++ b/packages/kilo-docs/pages/ai-providers/zenmux.md @@ -1,9 +1,9 @@ --- -title: ZenMux +title: "Using ZenMux with Kilo Code | Unified AI Gateway" +description: "Access OpenAI, Anthropic, Google, and other AI models through ZenMux in Kilo Code with automatic routing and cost optimization." +sidebar_label: ZenMux --- -import Codicon from "@site/src/components/Codicon"; - # Using ZenMux With Kilo Code [ZenMux](https://zenmux.ai) provides a unified API gateway to access multiple AI models from different providers through a single endpoint. It supports OpenAI, Anthropic, Google, and other major AI providers, automatically handling routing, fallbacks, and cost optimization. @@ -19,7 +19,7 @@ import Codicon from "@site/src/components/Codicon"; {% tabs %} {% tab label="VSCode (Legacy)" %} -1. **Open Kilo Code Settings:** Click the gear icon () in the Kilo Code panel. +1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. 2. **Select Provider:** Choose "ZenMux" from the "API Provider" dropdown. 3. **Enter API Key:** Paste your ZenMux API key into the "ZenMux API Key" field. 4. **Select Model:** Choose your desired model from the "Model" dropdown. @@ -70,7 +70,7 @@ Then set your default model: ZenMux supports a wide range of models from various providers: -Visi [zenmux.ai/models](https://zenmux.ai/models) to see the complete list of available models. +Visit [zenmux.ai/models](https://zenmux.ai/models) to see the complete list of available models. ### Other Providers diff --git a/packages/kilo-docs/pages/automate/agent-manager-workflows.md b/packages/kilo-docs/pages/automate/agent-manager-workflows.md index abf6f34327c..1def0846199 100644 --- a/packages/kilo-docs/pages/automate/agent-manager-workflows.md +++ b/packages/kilo-docs/pages/automate/agent-manager-workflows.md @@ -16,7 +16,11 @@ If you already use the sidebar chat and want to start running multiple agents in Rule of thumb: if you would stash or switch branches to do the work, create a worktree instead. {% callout type="info" %} -All Agent Manager sessions share a single `kilo serve` process. What each worktree isolates is the filesystem and git state — the branch, the directory, the terminal. API keys, models, and configuration are shared. +All Agent Manager sessions use the extension's embedded runtime. What each worktree isolates is the filesystem and git state: the branch, the directory, and the terminal. Providers, BYOK keys, custom providers, models, and extension settings are shared with the sidebar. +{% /callout %} + +{% callout type="warning" %} +Git worktrees are lightweight compared with cloning the repository several times, but they are not free. Each worktree has its own checked-out files, and any dependencies, build artifacts, caches, local databases, or generated files created inside that directory count separately on disk. {% /callout %} ## What parallelizes well @@ -102,6 +106,47 @@ Two fixes, in order of preference: The same applies to caches (avoid pointing `CARGO_TARGET_DIR` at a shared path), emulators (create a named simulator per worktree), and containers (use unique container names or `COMPOSE_PROJECT_NAME`). +A practical run script pattern is to derive a stable port from `WORKTREE_PATH`, then start the app with that value: + +```sh +#!/bin/sh +set -e + +sum=$(cksum <.models` in your config file. See [Custom Models](/docs/code-with-ai/agents/custom-models) for full details and examples. {% /callout %} +### Formatter and LSP Toggles + +Set `formatter` or `lsp` to `true` to use built-in defaults, or `false` to disable the feature completely: + +```jsonc +{ + "formatter": true, + "lsp": false, +} +``` + +Both keys also accept object configuration for specific tools or language servers. Custom LSP server entries must include an `extensions` array unless the entry disables a built-in server: + +```jsonc +{ + "lsp": { + "my-language-server": { + "command": ["my-lsp", "--stdio"], + "extensions": [".foo"], + }, + }, +} +``` + +### TUI Keybindings on Windows + +The TUI gives `Ctrl+Z` to input undo on Windows because native Windows terminals do not support POSIX terminal suspend. On Windows, `input_undo` defaults to `ctrl+z,ctrl+-,super+z` and `terminal_suspend` is disabled. On macOS and Linux, `terminal_suspend` defaults to `ctrl+z`. + +### OpenTelemetry Export + +Kilo telemetry is enabled by default and can be disabled with `experimental.openTelemetry = false`: + +```jsonc +{ + "experimental": { + "openTelemetry": false, + }, +} +``` + +If `OTEL_EXPORTER_OTLP_ENDPOINT` is set, the CLI exports OpenTelemetry traces and logs to that OTLP HTTP endpoint. You can also pass `OTEL_EXPORTER_OTLP_HEADERS` as comma-separated `key=value` pairs and `OTEL_RESOURCE_ATTRIBUTES` as comma-separated resource attributes. Request spans include `http.method`, `http.path`, route params such as `session.id` and `message.id`, and internal params under the `opencode.*` namespace. + ### Environment Variables Use `{env:VARIABLE_NAME}` syntax in config files to reference environment variables: diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/vscode/index.md b/packages/kilo-docs/pages/code-with-ai/platforms/vscode/index.md index 2db22d89a61..e539edf26b4 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/vscode/index.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/vscode/index.md @@ -5,7 +5,7 @@ description: "Using Kilo Code in Visual Studio Code" # VS Code Extension -Kilo Code is available as two VS Code extensions: the **VSCode (Legacy)** extension and the current **VSCode** version built on the Kilo CLI core. +Kilo Code is available as two VS Code extensions: the **VSCode (Legacy)** extension and the current **VSCode** version built on Kilo's shared agent runtime. {% tabs %} {% tab label="VSCode" %} @@ -17,7 +17,7 @@ Kilo Code is available as two VS Code extensions: the **VSCode (Legacy)** extens 3. Search for "Kilo Code" 4. Click the dropdown arrow next to **Install** and select **Install Pre-Release Version** -The extension bundles its own CLI binary and spawns `kilo serve` as a background process. All communication happens over HTTP + SSE. +The extension includes its own embedded runtime. No separate Kilo CLI installation is required. ## Key Features @@ -38,7 +38,7 @@ Key features include: ## Shared Settings -The extension shares its configuration with the CLI. Settings in `~/.config/kilo/kilo.jsonc` (global) and `./kilo.jsonc` (project) apply to both the CLI and the extension. +Settings apply across extension surfaces, including the sidebar and Agent Manager. The standalone CLI uses the same `~/.config/kilo/kilo.jsonc` (global) and `./kilo.jsonc` (project) files when used directly. {% /tab %} {% tab label="VSCode (Legacy)" %} diff --git a/packages/kilo-docs/pages/contributing/architecture/index.md b/packages/kilo-docs/pages/contributing/architecture/index.md index 8c1db4d8cdd..bbc3e1fe482 100644 --- a/packages/kilo-docs/pages/contributing/architecture/index.md +++ b/packages/kilo-docs/pages/contributing/architecture/index.md @@ -210,22 +210,35 @@ const client = new KiloClient({ baseUrl: "http://localhost:3000" }) const session = await client.session.create({ ... }) ``` -### Namespace Module Pattern +### Module Export Pattern -The CLI uses a namespace module pattern for organizing related functionality: +The CLI uses flat ESM exports inside each module, then re-exports the module as a namespace from an index file when callers need grouped access. Avoid adding new `export namespace` declarations; top-level exports are easier to tree-shake and work better with Node's type-stripping runtime. ```typescript -export namespace Session { - export const create = fn(CreateSchema, async (input) => { - // ... - }) +// packages/opencode/src/session/session.ts +export const create = fn(CreateSchema, async (input) => { + // ... +}) - export const list = fn(ListSchema, async (input) => { - // ... - }) -} +export const list = fn(ListSchema, async (input) => { + // ... +}) + +// packages/opencode/src/session/index.ts +export * as Session from "./session" ``` +Prefer importing the specific export when possible. Use the namespace re-export (`Session.create`, `Session.list`) when a caller benefits from grouped module access or when preserving the existing public shape. + +### CLI Server API + +The CLI server is Hono-based and publishes an OpenAPI-compatible HTTP + SSE API consumed by `@kilocode/sdk`. Some route groups are being migrated behind the experimental Effect `HttpApi` bridge while preserving the generated SDK shape. + +- Keep the SDK output stable when moving routes between Hono and Effect `HttpApi`. +- Use `KILO_EXPERIMENTAL_HTTPAPI` only for migration testing; public clients should not depend on the bridge detail. +- Regenerate `packages/sdk/js/` after server endpoint changes. +- Keep request handling observable with route spans and stable attributes where possible. + ### Tool Implementation Tools follow a consistent pattern with Zod schema validation: diff --git a/packages/kilo-docs/pages/contributing/architecture/per-message-feedback.md b/packages/kilo-docs/pages/contributing/architecture/per-message-feedback.md new file mode 100644 index 00000000000..b638dba13d2 --- /dev/null +++ b/packages/kilo-docs/pages/contributing/architecture/per-message-feedback.md @@ -0,0 +1,75 @@ +--- +title: "Per-Message Feedback" +description: "Thumbs up/down feedback on assistant messages sent to Kilo via telemetry" +--- + +# Per-Message Feedback (Thumbs Up / Down) + +## Problem + +We have no signal on which assistant responses are helpful and which aren't. Without per-response feedback, we can't: + +- Correlate model or prompt changes to user-perceived quality +- Identify specific bad responses in the Kilo Gateway logs for investigation +- Detect patterns where certain providers, models, or prompt paths consistently underperform + +Aggregate metrics like session completion rate or token cost are too coarse to understand individual response quality. A lightweight thumbs-up/down on each message can help close the feedback loop. + +## Proposal + +Add a thumbs-up / thumbs-down widget next to the existing copy button on every assistant message. Ratings are sent to Kilo via the existing PostHog telemetry pipeline. The UI is hidden entirely when telemetry is disabled. + +### Scope + +| Surface | Approach | +|---|---| +| VS Code extension | Thumbs buttons inline next to the copy button | +| TUI | Keybinds (`+` / `-`) on the last assistant message | + +### Telemetry Payload + +We deliberately collect fewer identifiers for non-Kilo providers, since those IDs can't be correlated to upstream data and add tracking surface without product benefit. Users of non-Kilo GW models would also not expect or want us to collect that information in Kilo GW from other providers. + +**Third party providers (Anthropic, OpenAI, local, etc.):** +`providerID`, `modelID`, `variant?`, `rating`, `previousRating?` + +**Kilo Gateway turns (`providerID` starts with `"kilo"`):** +Same fields plus `sessionID`, `messageID`, and `parentMessageID` (= the `x-kilo-request` header the gateway already saw). This lets backend analysts join feedback against gateway logs to diagnose specific bad responses. + +Event name: `"Feedback Submitted"` — a single event string in both telemetry enum registries so PostHog sees one event regardless of source. + +### UX + +- **Toggleable**: click the same button again to clear, or click the opposite to switch. Each change fires a new event with `rating` and `previousRating`. +- **In-memory state**: ratings are keyed by message ID and held in the webview/TUI session. Persisting across reloads is deferred to a follow-up. +- **Gated on telemetry**: if the user has VS Code telemetry disabled, the buttons don't render at all. For the CLI when telemetry is off, the keybinds are no-ops. + +### Architecture (high level) + +``` +[webview button / TUI keybind] + → existing telemetry proxy or Telemetry.track() + → POST /telemetry/capture (webview path) + → Telemetry.track("Feedback Submitted", {…}) + → PostHog +``` + +No new server endpoints, no SDK regeneration, no PostHog-side changes. The `/telemetry/capture` route and both telemetry proxy paths already exist and accept arbitrary event names. + +### Kilo Gateway Detection + +The webview uses `providerID.startsWith("kilo")` to decide whether to include correlation IDs — this matches the outbound header gating in `packages/opencode/src/session/llm.ts`. The TUI can use the more precise `model.api.npm === "@kilocode/kilo-gateway"` check since it has access to the full provider resolution in-process. + +## What's Out of Scope + +- Free-text comments on thumbs-down +- 1–5 scale or star rating +- Persisting ratings across page reloads / session switches +- Changing prior-message actions (copy + thumbs) to hover-only +- Shared web UI / desktop surface + +## Open Questions + +- Should ratings persist on the `MessageV2.Assistant` schema so they survive reloads? +- Confirm with the PostHog dashboard owner that the proposed event + property names fit existing conventions. +- Whether to add free-text comments for thumbs-down in a follow-up. diff --git a/packages/kilo-docs/pages/customize/context/codebase-indexing.md b/packages/kilo-docs/pages/customize/context/codebase-indexing.md index 163afeaa00c..f577678670b 100644 --- a/packages/kilo-docs/pages/customize/context/codebase-indexing.md +++ b/packages/kilo-docs/pages/customize/context/codebase-indexing.md @@ -1,14 +1,15 @@ --- title: "Codebase Indexing" description: "Index your codebase for improved AI understanding" -platform: legacy --- # Codebase Indexing Codebase Indexing enables semantic code search across your entire project using AI embeddings. Instead of searching for exact text matches, it understands the _meaning_ of your queries, helping Kilo Code find relevant code even when you don't know specific function names or file locations. -{% image src="/docs/img/codebase-indexing/codebase-indexing.png" alt="Codebase Indexing Settings" width="800" caption="Codebase Indexing Settings" /%} +{% callout type="warning" title="Experimental" %} +Codebase Indexing is currently **experimental** in the CLI and the new VS Code extension. You must explicitly opt in before the feature becomes available — see the **Setup** section below. Behavior, configuration, and defaults may change in future releases. +{% /callout %} ## What It Does @@ -16,7 +17,7 @@ When enabled, the indexing system: 1. **Parses your code** using Tree-sitter to identify semantic blocks (functions, classes, methods) 2. **Creates embeddings** of each code block using AI models -3. **Stores vectors** in a Qdrant database for fast similarity search +3. **Stores vectors** in a vector database for fast similarity search 4. **Provides the [`semantic_search`](/docs/automate/tools/semantic-search) tool** to Kilo Code for intelligent code discovery This enables natural language queries like "user authentication logic" or "database connection handling" to find relevant code across your entire project. @@ -28,43 +29,217 @@ This enables natural language queries like "user authentication logic" or "datab - **Cross-Project Discovery**: Search across all files, not just what's open - **Pattern Recognition**: Locate similar implementations and code patterns -## Setup Requirements +## Setup -### Embedding Provider +{% tabs %} +{% tab label="VSCode" %} -Choose one of these options for generating embeddings: +### 1. Enable the experimental flag -**OpenAI (Recommended)** +Codebase Indexing is gated behind an experimental flag. Until the flag is on, the Indexing UI is hidden and `semantic_search` is unavailable. -- Requires OpenAI API key -- Supports all OpenAI embedding models -- Default: `text-embedding-3-small` -- Processes up to 100,000 tokens per batch +1. Open Kilo Code **Settings** → **Experimental**. +2. Toggle **Semantic Indexing** on. +3. The **Indexing** tab will appear in Settings and the indexing status indicator will appear at the bottom of the prompt input panel. -**Gemini** +Alternatively, set `experimental.semantic_indexing` to `true` in your `kilo.jsonc`: -- Requires Google AI API key -- Supports Gemini embedding models including `gemini-embedding-001` -- Cost-effective alternative to OpenAI -- High-quality embeddings for code understanding +```json +{ + "experimental": { + "semantic_indexing": true + } +} +``` -**Ollama (Local)** +### 2. Configure indexing -- Requires local Ollama installation -- No API costs or internet dependency -- Supports any Ollama-compatible embedding model -- Requires Ollama base URL configuration +1. Open Kilo Code **Settings** → **Indexing**, or click the indexing indicator at the bottom of the prompt input panel. +2. Toggle **Enable Indexing** on. +3. Pick an **Embedding Provider** and fill in its required fields. +4. Pick a **Vector Store** (`Qdrant` or `LanceDB`) and configure it. +5. Optionally adjust **Tuning Parameters** (search score, batch size, retries, max results). +6. Save to start the initial scan. -### Vector Database +You can also edit the `indexing` section in `kilo.jsonc` directly: -**Qdrant** is required for storing and searching embeddings: +```json +{ + "indexing": { + "enabled": true, + "provider": "openai", + "model": "text-embedding-3-small", + "vectorStore": "lancedb", + "openai": { "apiKey": "sk-..." }, + "lancedb": {} + } +} +``` -- **Local**: `http://localhost:6333` (recommended for testing) -- **Cloud**: Qdrant Cloud or self-hosted instance -- **Authentication**: Optional API key for secured deployments +### Embedding providers + +| Provider | How to use | Notes | +|---|---|---| +| **OpenAI** | API key | Default model: `text-embedding-3-small`. `text-embedding-3-large` for higher accuracy. | +| **Ollama** | Local base URL | No API costs. Runs fully offline. | +| **OpenAI-Compatible** | Base URL + API key | For self-hosted or third-party OpenAI-compatible endpoints. | +| **Gemini** | Google AI API key | Supports `gemini-embedding-001` and other Gemini embedding models. | +| **Mistral** | API key from [La Plateforme](https://console.mistral.ai/api-keys/) | Use a standard Mistral API key. The Codestral-specific keys from the [Mistral autocomplete setup guide](/docs/code-with-ai/features/autocomplete/mistral-setup) are **not** interchangeable — those only work for completion. | +| **Vercel AI Gateway** | API key | Routes requests through [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). | +| **AWS Bedrock** | AWS region + profile | Uses the AWS SDK credential chain. | +| **OpenRouter** | API key (optional specific provider) | Routes through [OpenRouter](https://openrouter.ai/). | +| **Voyage** | API key | Voyage `voyage-code-3` is tuned for code. | + +### Vector stores + +- **Qdrant** (default) — external server. Recommended for team deployments and larger codebases. See [Setting Up Qdrant](#setting-up-qdrant). +- **LanceDB** — embedded, file-based. No server to run. Stores data under your Kilo data directory by default. + +{% callout type="tip" %} +For a fully local, zero-cost setup, combine **Ollama** (embeddings) with **LanceDB** (vector store — no separate server needed). +{% /callout %} + +### Status indicator + +The prompt input panel shows a compact indexing status indicator that reflects the current state (Standby / In Progress / Complete / Error) along with progress when scanning or embedding. + +{% /tab %} +{% tab label="CLI" %} + +### 1. Enable the experimental flag + +Codebase Indexing is gated behind an experimental flag. Until the flag is on, the `/indexing` command is hidden and `semantic_search` is unavailable. + +Set the flag in your `kilo.jsonc`: + +```json +{ + "experimental": { + "semantic_indexing": true + } +} +``` + +Restart the CLI for the change to take effect. The `/indexing` command (and aliases `/index`, `/embedding`) will appear in the command palette once the flag is active. + +### 2. Configure indexing + +Open a Kilo TUI session and run: + +```text +/indexing +``` + +(aliases: `/index`, `/embedding`) + +This opens an interactive configuration dialog where you can: + +- **Toggle** indexing on/off +- Choose an **Embedding Provider** and fill in provider settings (API key, base URL, AWS region, etc.) +- Set the **Embedding Model** (blank = provider default) +- Set the **Vector Dimension** (blank = auto-detect from the model) +- Choose a **Vector Store** (`Qdrant` or `LanceDB`) and configure its connection +- Adjust **Tuning Parameters** (search threshold, batch size, retries, max results) + +All changes are written to your `kilo.jsonc` config and take effect immediately. + +You can also edit the `indexing` section directly. This is the full shape of the section: + +```json +{ + "indexing": { + "enabled": true, + "provider": "voyage", + "model": "voyage-code-3", + "dimension": 1024, + "vectorStore": "qdrant", + "voyage": { + "apiKey": "pa-..." + }, + "qdrant": { + "url": "http://localhost:6333", + "apiKey": "" + }, + "searchMinScore": 0.4, + "searchMaxResults": 50, + "embeddingBatchSize": 60, + "scannerMaxBatchRetries": 3 + } +} +``` + +### Embedding providers + +| Provider | Config key | Settings | Notes | +|---|---|---|---| +| **OpenAI** | `openai` | `{ apiKey }` | Default: `text-embedding-3-small`. | +| **Ollama** | `ollama` | `{ baseUrl }` | No API costs. Runs fully offline. | +| **OpenAI-Compatible** | `openai-compatible` | `{ baseUrl, apiKey }` | For self-hosted or third-party endpoints. | +| **Gemini** | `gemini` | `{ apiKey }` | Supports `gemini-embedding-001`. | +| **Mistral** | `mistral` | `{ apiKey }` | Use a [La Plateforme](https://console.mistral.ai/api-keys/) key — the Codestral-specific keys from the [autocomplete setup guide](/docs/code-with-ai/features/autocomplete/mistral-setup) don't work for embeddings. | +| **Vercel AI Gateway** | `vercel-ai-gateway` | `{ apiKey }` | Routes through [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). | +| **AWS Bedrock** | `bedrock` | `{ region, profile }` | Uses AWS SDK credential chain. | +| **OpenRouter** | `openrouter` | `{ apiKey, specificProvider? }` | Routes through [OpenRouter](https://openrouter.ai/). | +| **Voyage** | `voyage` | `{ apiKey }` | `voyage-code-3` is tuned for code. | + +### Vector stores + +- `qdrant` — `{ url?, apiKey? }` (default). See [Setting Up Qdrant](#setting-up-qdrant). +- `lancedb` — `{ directory? }` — embedded, file-based. No server to run. Uses a default Kilo data directory when omitted. + +{% callout type="tip" %} +For a fully local, zero-cost setup, combine **Ollama** (embeddings) with **LanceDB** (vector store — no separate server needed). +{% /callout %} + +### Status indicator + +When indexing is enabled, the CLI shows an indexing status badge at the bottom of the TUI in the form `IDX ` (for example `IDX In Progress 40% 120/300`, `IDX Complete`, `IDX Standby`, or `IDX Error `). + +{% /tab %} +{% tab label="VSCode (Legacy)" %} + +The legacy extension does not require an experimental flag. + +### Open Codebase Indexing Settings + +1. In the chat header, click the database icon (indexing status). +2. The Codebase Indexing settings panel opens. +3. If you don't see the icon, open Kilo Code settings ({% codicon name="gear" /%}) and search for **Codebase Indexing**. + +{% image src="/docs/img/codebase-indexing/codebase-indexing.png" alt="Codebase Indexing Settings" width="800" caption="Codebase Indexing Settings (legacy)" /%} + +### Configure Settings + +1. Enable **"Enable Codebase Indexing"** using the toggle switch. +2. Configure your embedding provider: + - **OpenAI**: Enter API key and select model + - **Gemini**: Enter Google AI API key and select embedding model + - **Ollama**: Enter base URL and select model +3. Set Qdrant URL and optional API key. +4. Configure **Max Search Results** (default: 20, range: 1-100). +5. Click **Save** to start initial indexing. + +### Embedding providers + +The legacy extension supports a smaller set of providers: + +| Provider | How to use | Notes | +|---|---|---| +| **OpenAI** | API key | Default: `text-embedding-3-small`. | +| **Gemini** | Google AI API key | Supports Gemini embedding models including `gemini-embedding-001`. | +| **Ollama (local)** | Local base URL | No API costs. | + +### Vector store + +The legacy extension only supports **Qdrant**. See [Setting Up Qdrant](#setting-up-qdrant). + +{% /tab %} +{% /tabs %} ## Setting Up Qdrant +If you choose **Qdrant** as your vector store, you need a running Qdrant server. + ### Quick Local Setup **Using Docker:** @@ -92,55 +267,27 @@ volumes: For team or production use: -- [Qdrant Cloud](https://cloud.qdrant.io/) - Managed service +- [Qdrant Cloud](https://cloud.qdrant.io/) — managed service - Self-hosted on AWS, GCP, or Azure - Local server with network access for team sharing -## Configuration - -### Open Codebase Indexing Settings - -1. In the chat header, click the database icon (indexing status) -2. The Codebase Indexing settings panel opens -3. If you don't see the icon, open Kilo Code settings () and search for **Codebase Indexing** - -### Configure Settings - -1. Enable **"Enable Codebase Indexing"** using the toggle switch -2. Configure your embedding provider: - - **OpenAI**: Enter API key and select model - - **Gemini**: Enter Google AI API key and select embedding model - - **Ollama**: Enter base URL and select model -3. Set Qdrant URL and optional API key -4. Configure **Max Search Results** (default: 20, range: 1-100) -5. Click **Save** to start initial indexing - -### Enable/Disable Toggle - -The codebase indexing feature includes a convenient toggle switch that allows you to: - -- **Enable**: Start indexing your codebase and make the search tool available -- **Disable**: Stop indexing, pause file watching, and disable the search functionality -- **Preserve Settings**: Your configuration remains saved when toggling off - -This toggle is useful for temporarily disabling indexing during intensive development work or when working with sensitive codebases. - ## Understanding Index Status -The interface shows real-time status with color indicators: +The interface shows real-time status: -- **Standby** (Gray): Not running, awaiting configuration -- **Indexing** (Yellow): Currently processing files -- **Indexed** (Green): Up-to-date and ready for searches -- **Error** (Red): Failed state requiring attention +- **Standby**: Not running, awaiting configuration or paused +- **In Progress**: Currently processing files (with a progress percentage and `processed/total` count) +- **Complete**: Up-to-date and ready for searches +- **Error**: Failed state, with an error message +- **Disabled**: Indexing is turned off or not yet configured ## How Files Are Processed ### Smart Code Parsing - **Tree-sitter Integration**: Uses AST parsing to identify semantic code blocks -- **Language Support**: All languages supported by Tree-sitter -- **Markdown Support**: Full support for markdown files and documentation +- **Language Support**: Broad language coverage via Tree-sitter — C, C#, C++, CSS, Elisp, Elixir, Go, HTML, Java, JavaScript, Kotlin, Lua, OCaml, PHP, Python, Ruby, Rust, Scala, Solidity, Swift, SystemRDL, TLA+, TOML, TSX, TypeScript, Vue, Zig, and more +- **Markdown Support**: Dedicated parser for markdown and documentation - **Fallback**: Line-based chunking for unsupported file types - **Block Sizing**: - Minimum: 100 characters @@ -159,40 +306,56 @@ The indexer automatically excludes: ### Incremental Updates -- **File Watching**: Monitors workspace for changes +- **File Watching**: Monitors the workspace for changes and re-indexes in the background - **Smart Updates**: Only reprocesses modified files - **Hash-based Caching**: Avoids reprocessing unchanged content - **Branch Switching**: Automatically handles Git branch changes +## Tuning Parameters + +These advanced settings live under the `indexing` key and are exposed in the CLI's `/indexing → Tuning Parameters` menu and the VS Code extension's Indexing settings: + +| Setting | Default | Description | +|---|---|---| +| `searchMinScore` | `0.4` | Minimum cosine similarity (0-1) for a result to be returned. | +| `searchMaxResults` | `50` | Maximum number of results returned per search. | +| `embeddingBatchSize` | `60` | Number of code segments per embedding batch. Lower this if your embedding endpoint has strict rate limits. | +| `scannerMaxBatchRetries` | `3` | Maximum retry attempts for a failed embedding batch. | + ## Best Practices ### Model Selection -**For OpenAI:** +**OpenAI:** - **`text-embedding-3-small`**: Best balance of performance and cost - **`text-embedding-3-large`**: Higher accuracy, 5x more expensive - **`text-embedding-ada-002`**: Legacy model, lower cost -**For Ollama:** +**Ollama:** -- **`mxbai-embed-large`**: The largest and highest-quality embedding model. -- **`nomic-embed-text`**: Best balance of performance and embedding quality. -- **`all-minilm`**: Compact model with lower quality but faster performance. +- **`mxbai-embed-large`**: The largest and highest-quality embedding model +- **`nomic-embed-text`**: Best balance of performance and embedding quality +- **`all-minilm`**: Compact model with lower quality but faster performance + +**Voyage:** + +- **`voyage-code-3`**: Code-tuned embeddings; strong default for source-heavy repos ### Security Considerations -- **API Keys**: Stored securely in VS Code's encrypted storage -- **Code Privacy**: Only small code snippets sent for embedding (not full files) -- **Local Processing**: All parsing happens locally -- **Qdrant Security**: Use authentication for production deployments +- **API Keys**: Stored in your `kilo.jsonc` config. Treat that file as a secret in shared environments. +- **Code Privacy**: Only small code snippets are sent for embedding — never whole files. +- **Local Processing**: All parsing (Tree-sitter) happens locally. +- **Fully Local Option**: Pair **Ollama** (embeddings) with **LanceDB** (local vector store) for a setup that never leaves your machine. +- **Qdrant Security**: Use authentication for production deployments. ## Current Limitations - **File Size**: 1MB maximum per file - **Single Workspace**: One workspace at a time -- **Dependencies**: Requires external services (embedding provider + Qdrant) -- **Language Coverage**: Limited to Tree-sitter supported languages for optimal parsing +- **Dependencies**: Requires an embedding provider, and — for Qdrant — a running Qdrant instance +- **Language Coverage**: Optimal parsing is limited to Tree-sitter supported languages ## Troubleshooting @@ -200,6 +363,16 @@ The indexer automatically excludes: If your local embedding server is based on llama.cpp (including Ollama), indexing can fail with errors about `n_ubatch` or `GGML_ASSERT`. Ensure both batch size (`-b`) and micro-batch size (`-ub`) are set to the same value for embedding models, then restart the server. For Ollama, configure `num_batch` in your Modelfile or request options to match the same effective value. +### Indexing status stays on "Disabled" + +- Check that `indexing.enabled` is `true` in your `kilo.jsonc` +- Verify that the selected provider has all required credentials set +- If using Qdrant, make sure the Qdrant server is reachable at the configured URL + +### Rate-limit or batch errors with a hosted provider + +Lower `embeddingBatchSize` under `indexing` (default `60`). Smaller batches send fewer segments per request and are less likely to hit per-request or per-minute rate limits. + ## Using the Search Feature Once indexed, Kilo Code can use the [`semantic_search`](/docs/automate/tools/semantic-search) tool to find relevant code: @@ -213,34 +386,22 @@ Once indexed, Kilo Code can use the [`semantic_search`](/docs/automate/tools/sem The tool provides Kilo Code with: -- Relevant code snippets (up to your configured max results limit) +- Relevant code snippets (up to your configured `searchMaxResults`) - File paths and line numbers - Similarity scores - Contextual information ### Search Results Configuration -You can control the number of search results returned by adjusting the **Max Search Results** setting: +Tune result volume and quality via: -- **Default**: 20 results -- **Range**: 1-100 results -- **Performance**: Lower values improve response speed -- **Comprehensiveness**: Higher values provide more context but may slow responses +- **`searchMaxResults`** — default `50`. Lower for faster, more focused responses; higher for more context. +- **`searchMinScore`** — default `0.4`. Raise to require closer matches; lower to include more tangentially related code. ## Privacy & Security -- **Code stays local**: Only small code snippets sent for embedding +- **Code stays local**: Only small code snippets are sent for embedding - **Embeddings are numeric**: Not human-readable representations -- **Secure storage**: API keys encrypted in VS Code storage -- **Local option**: Use Ollama for completely local processing -- **Access control**: Respects existing file permissions - -## Future Enhancements - -Planned improvements: - -- Additional embedding providers -- Multi-workspace indexing -- Enhanced filtering and configuration options -- Team sharing capabilities -- Integration with VS Code's native search +- **Secure storage**: API keys are stored in your local `kilo.jsonc` configuration +- **Fully local option**: Use **Ollama + LanceDB** for completely local processing +- **Access control**: Respects existing file permissions and `.kilocodeignore` diff --git a/packages/kilo-docs/pages/customize/context/context-condensing.md b/packages/kilo-docs/pages/customize/context/context-condensing.md index 8fc83cf763f..9993b4bcca3 100644 --- a/packages/kilo-docs/pages/customize/context/context-condensing.md +++ b/packages/kilo-docs/pages/customize/context/context-condensing.md @@ -22,15 +22,15 @@ Every AI model has a maximum context window — a limit on how much text it can ## The Solution: Auto-Compaction -Kilo Code uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces a structured summary that captures: +Kilo Code uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces an anchored summary that captures: - The overall goal of the session -- Instructions given along the way -- Key discoveries made -- What has been accomplished so far +- Constraints and preferences you gave along the way +- Progress, key decisions, and next steps +- Critical context needed to continue - Relevant files and directories -This summary replaces the earlier conversation history, freeing up context window space while maintaining continuity in your work. +This summary replaces older conversation history while Kilo keeps the most recent turns verbatim when they fit. If a session has already been compacted, Kilo updates the previous summary instead of starting over, preserving still-relevant details and removing stale ones. ## How Compaction Triggers @@ -60,6 +60,8 @@ You can trigger compaction at any time: |---|---|---| | `compaction.auto` | `true` | Automatically compact when the usable window is reached | | `compaction.prune` | `true` | Clear old tool outputs beyond the 40K recency window | +| `compaction.tail_turns` | `2` | Keep the most recent user turns and their responses verbatim when possible | +| `compaction.preserve_recent_tokens` | 25% of usable context, clamped between 2,000 and 8,000 tokens | Token budget for the verbatim recent tail | | `compaction.reserved` | `min(20,000, model_max_output_tokens)` | Token headroom kept free for the next turn — also defines the compaction trigger point | ## Configuration @@ -71,6 +73,8 @@ Compaction is configured in your `kilo.jsonc` file: "compaction": { "auto": true, // Enable or disable automatic compaction "prune": true, // Enable pruning of old tool outputs beyond the recency window + "tail_turns": 2, // Recent user turns to keep verbatim during compaction + "preserve_recent_tokens": 8000, // Maximum token budget for the recent tail "reserved": 20000, // Token buffer kept free; smaller = later trigger, larger = earlier trigger }, } @@ -80,6 +84,8 @@ Compaction is configured in your `kilo.jsonc` file: |---|---|---|---| | `compaction.auto` | boolean | `true` | Enable or disable automatic compaction when the usable window is reached | | `compaction.prune` | boolean | `true` | Enable pruning of old tool outputs outside the 40K token recency window | +| `compaction.tail_turns` | number | `2` | Number of recent user turns, including following assistant and tool responses, to keep verbatim during compaction | +| `compaction.preserve_recent_tokens` | number | 25% of usable context, clamped between 2,000 and 8,000 tokens | Maximum token budget for recent turns kept verbatim after compaction | | `compaction.reserved` | number | `min(20000, model_max_output)` | Token headroom reserved for the next turn. Applies only to models that advertise a separate input limit; models with a single context window use their full output cap as the reserve instead. | ### Use a different model for compaction @@ -111,15 +117,15 @@ If no compaction agent is set, the current session's model is used. ## The Solution: Auto-Compaction -Kilo CLI uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces a structured summary that captures: +Kilo CLI uses a **Compaction** system to manage context automatically. When your conversation approaches the token limit, compaction kicks in and produces an anchored summary that captures: - The overall goal of the session -- Instructions given along the way -- Key discoveries made -- What has been accomplished so far +- Constraints and preferences you gave along the way +- Progress, key decisions, and next steps +- Critical context needed to continue - Relevant files and directories -This summary replaces the earlier conversation history, freeing up context window space while maintaining continuity in your work. +This summary replaces older conversation history while Kilo keeps the most recent turns verbatim when they fit. If a session has already been compacted, Kilo updates the previous summary instead of starting over, preserving still-relevant details and removing stale ones. ## How Compaction Triggers @@ -148,6 +154,8 @@ You can trigger compaction at any time: |---|---|---| | `compaction.auto` | `true` | Automatically compact when the usable window is reached | | `compaction.prune` | `true` | Clear old tool outputs beyond the 40K recency window | +| `compaction.tail_turns` | `2` | Keep the most recent user turns and their responses verbatim when possible | +| `compaction.preserve_recent_tokens` | 25% of usable context, clamped between 2,000 and 8,000 tokens | Token budget for the verbatim recent tail | | `compaction.reserved` | `min(20,000, model_max_output_tokens)` | Token headroom kept free for the next turn — also defines the compaction trigger point | ## Configuration @@ -159,6 +167,8 @@ Compaction is configured in your `kilo.jsonc` file: "compaction": { "auto": true, // Enable or disable automatic compaction "prune": true, // Enable pruning of old tool outputs beyond the recency window + "tail_turns": 2, // Recent user turns to keep verbatim during compaction + "preserve_recent_tokens": 8000, // Maximum token budget for the recent tail "reserved": 20000, // Token buffer kept free; smaller = later trigger, larger = earlier trigger }, } @@ -168,6 +178,8 @@ Compaction is configured in your `kilo.jsonc` file: |---|---|---|---| | `compaction.auto` | boolean | `true` | Enable or disable automatic compaction when the usable window is reached | | `compaction.prune` | boolean | `true` | Enable pruning of old tool outputs outside the 40K token recency window | +| `compaction.tail_turns` | number | `2` | Number of recent user turns, including following assistant and tool responses, to keep verbatim during compaction | +| `compaction.preserve_recent_tokens` | number | 25% of usable context, clamped between 2,000 and 8,000 tokens | Maximum token budget for recent turns kept verbatim after compaction | | `compaction.reserved` | number | `min(20000, model_max_output)` | Token headroom reserved for the next turn. Applies only to models that advertise a separate input limit; models with a single context window use their full output cap as the reserve instead. | ### Use a different model for compaction diff --git a/packages/kilo-docs/pages/customize/context/large-projects.md b/packages/kilo-docs/pages/customize/context/large-projects.md index 101fb1621b6..cfa129e40b1 100644 --- a/packages/kilo-docs/pages/customize/context/large-projects.md +++ b/packages/kilo-docs/pages/customize/context/large-projects.md @@ -31,7 +31,7 @@ The context window includes: 5. **Prioritize Recent History:** Kilo Code automatically truncates older messages in the conversation history to stay within the context window. Be mindful of this, and re-include important context if needed. -6. **Use Prompt Caching (if available):** Some API providers like Anthropic, OpenAI, OpenRouter and Requesty support "prompt caching". This caches your prompts for use in future tasks and helps reduce the cost and latency of requests. +6. **Use Prompt Caching (if available):** Some API providers like Anthropic, OpenAI, OpenRouter, Requesty, and Vertex AI Claude support "prompt caching". This caches your prompts for use in future tasks and helps reduce the cost and latency of requests. ## Example: Refactoring a Large File diff --git a/packages/kilo-docs/pages/customize/custom-modes.md b/packages/kilo-docs/pages/customize/custom-modes.md index e541d8b46aa..928a4feda93 100644 --- a/packages/kilo-docs/pages/customize/custom-modes.md +++ b/packages/kilo-docs/pages/customize/custom-modes.md @@ -151,7 +151,7 @@ permission: read: allow ``` -Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `list`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. +Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. ### `model` @@ -387,7 +387,7 @@ permission: read: allow ``` -Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `list`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. +Known permission types include: `read`, `edit`, `bash`, `glob`, `grep`, `task`, `webfetch`, `websearch`, `codesearch`, `todowrite`, `todoread`, and more. ### `model` diff --git a/packages/kilo-docs/pages/getting-started/byok.md b/packages/kilo-docs/pages/getting-started/byok.md index 3fd344f85d3..a9d17bb86b9 100644 --- a/packages/kilo-docs/pages/getting-started/byok.md +++ b/packages/kilo-docs/pages/getting-started/byok.md @@ -10,23 +10,41 @@ Bring Your Own Key (BYOK) lets you use your own API keys when using the Kilo Gat A user or organization may want to use BYOK to: - Utilize new models quickly, Kilo Gateway supports most new models in minutes -- Use subscriptions with third-party AI providers, for example [Z.AI](https://z.ai/subscribe) or [Minimax](https://platform.minimax.io/subscribe/coding-plan) +- Use subscriptions with third-party AI providers, for example the [Z.ai Coding Plan](https://z.ai/subscribe), [Kimi Code](https://platform.moonshot.ai/), or the [BytePlus Coding Plan](https://www.byteplus.com/) - Attribute usage against existing provider commitments or agreements - Use existing credits with a provider ## Supported BYOK providers -Kilo Gateway currently supports BYOK keys for these providers: +Kilo Gateway supports BYOK keys for these providers. + +### Standard API keys + +Use your provider API key to route matching models through your account: - Anthropic - AWS Bedrock +- Fireworks - Google AI Studio - Inception - Minimax - Mistral AI +- Moonshot AI (Kimi) +- Novita - OpenAI - xAI -- Z.AI +- Z.ai + +### Subscription and direct provider plans + +These providers offer coding-focused subscriptions or dedicated endpoints. Bring the API key issued by your plan to use its included models through the Kilo Gateway: + +- BytePlus Coding Plan +- Chutes BYOK +- Kimi Code +- Mistral Codestral +- Neuralwatt +- Z.ai Coding Plan ## Add a BYOK key @@ -63,13 +81,10 @@ Your IAM user or role must have the following permissions: - When you use the **Kilo Gateway** provider, Kilo checks if there's a BYOK key for the selected model's provider. - If a matching BYOK key exists, the request is routed using your key. - If the key is invalid, the request fails. It does not fall back to using Kilo's keys. +- Subscription-based providers (such as the Z.ai Coding Plan or Kimi Code) only expose the models included in that plan. Select one of those models to route traffic through your subscription. ## Using BYOK in the Extensions and CLI - BYOK works with the Kilo Gateway provider. Users should ensure that is set as the active [provider](/docs/ai-providers). -- Select a model from a provider configured for BYOK, for example Claude Sonnet 4.5 if you configured BYOK for Anthropic. +- Select a model from a provider configured for BYOK, for example Claude Sonnet 4.5 if you configured BYOK for Anthropic, or GLM-4.7 if you configured the Z.ai Coding Plan. - (Optional) Validate with the provider that traffic is being served by that key. - -## Limitations - -- BYOK is not fully supported by Agent Manager. See [Agent Manager](/docs/automate/agent-manager) for details. diff --git a/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md b/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md index fb797dc0f0a..8e4bee7cefb 100644 --- a/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md +++ b/packages/kilo-docs/pages/getting-started/settings/auto-approving-actions.md @@ -44,7 +44,6 @@ The Auto Approve tab lists the following tool-specific permissions. Some tools a | `edit` | Editing existing files | | `glob` | File pattern matching / searching by name | | `grep` | Searching file contents by regex | -| `list` | Listing directory contents | | `task` | Launching sub-agents | | `skill` | Loading specialized skills | | `lsp` | Language server protocol operations | @@ -109,7 +108,6 @@ Permissions are configured under the `permission` key in `kilo.jsonc`. The follo | `edit` | Editing existing files | | `glob` | File pattern matching / searching by name | | `grep` | Searching file contents by regex | -| `list` | Listing directory contents | | `task` | Launching sub-agents | | `skill` | Loading specialized skills | | `lsp` | Language server protocol operations | diff --git a/packages/kilo-docs/pages/getting-started/settings/index.md b/packages/kilo-docs/pages/getting-started/settings/index.md index 9f66c73005c..69e1953dbc9 100644 --- a/packages/kilo-docs/pages/getting-started/settings/index.md +++ b/packages/kilo-docs/pages/getting-started/settings/index.md @@ -5,7 +5,7 @@ description: "Configure Kilo Code settings and preferences" # Settings -The VS Code extension can be configured through the Settings window, opened by pressing the gear icon. Both the CLI and the extension can also be configured through interactions with the agent. The current VS Code extension and CLI share the same underlying settings, so changes in one are reflected in the other. +The VS Code extension can be configured through the Settings window, opened by pressing the gear icon in Kilo Code. Changes apply across extension surfaces, including the sidebar and Agent Manager. The CLI can also use the same JSONC config files when you use it directly. ## Configuring with the Agent @@ -30,9 +30,9 @@ This is especially useful for complex configuration like custom model definition {% tabs %} {% tab label="VSCode" %} -The VS Code extension provides a **Settings webview UI** accessible from the extension sidebar by clicking the gear icon ({% codicon name="gear" /%}). The UI is organized into tabs including Providers, Auto-Approve, Models, and more. +The VS Code extension provides a **Settings webview UI** accessible from Kilo Code by clicking the gear icon ({% codicon name="gear" /%}). The UI is organized into tabs including Providers, Auto-Approve, Models, and more. -This UI reads and writes to the same underlying JSONC config files used by the CLI, so changes made in either place are reflected in both. +This UI reads and writes to the same underlying JSONC config files used across extension surfaces. Changes apply to the sidebar, Agent Manager, and the CLI when used directly. ### Config File Locations @@ -45,6 +45,28 @@ There are two primary config files: If you check config files into version control, make sure they do not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead. {% /callout %} +### Reasoning Blocks + +Reasoning blocks stay expanded by default in the VS Code chat UI. Enable **Auto-Collapse Reasoning** in the Display tab, or set `auto_collapse_reasoning` in `kilo.jsonc`, to collapse them after the agent finishes writing them: + +```json +{ + "auto_collapse_reasoning": true +} +``` + +### Terminal Command Blocks + +Terminal command blocks stay expanded by default in the VS Code chat UI. Choose **Collapsed** for **Terminal Command Blocks** in the Display tab, or set `terminal_command_display` in `kilo.jsonc`, to start them collapsed: + +```json +{ + "terminal_command_display": "collapsed" +} +``` + +Valid values are `expanded` and `collapsed`. + ### Export and Import You can export and import settings from the **About Kilo Code** tab in the Settings UI: @@ -171,6 +193,7 @@ Available experimental toggles include: - **LSP integration** — expose language server diagnostics to the agent - **Paste summary** — summarize large clipboard pastes before including them - **Batch tool** — allow the agent to batch multiple tool calls in one step +- **OpenTelemetry** — enable Kilo telemetry and optional OTLP export when configured Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`: @@ -179,6 +202,7 @@ Advanced options not exposed in the UI can be configured via the `experimental` "experimental": { "codebase_search": true, "batch_tool": false, + "openTelemetry": true, "disable_paste_summary": false, "mcp_timeout": 30000 } @@ -190,7 +214,9 @@ Refer to the auto-generated `$schema` in your `kilo.jsonc` for the full list of {% /tab %} {% tab label="CLI" %} -The CLI does not currently expose the same experimental feature toggles as the **VSCode (Legacy)** version. Configuration of model behavior, file editing strategies, and other advanced options is handled directly in the JSONC config files. Refer to the auto-generated `$schema` in your `kilo.jsonc` for the full list of available options. +The CLI does not currently expose the same experimental feature toggles as the **VSCode (Legacy)** version. Configuration of model behavior, file editing strategies, telemetry, and other advanced options is handled directly in the JSONC config files. Refer to the auto-generated `$schema` in your `kilo.jsonc` for the full list of available options. + +Telemetry is enabled by default. Set `experimental.openTelemetry` to `false` in `kilo.jsonc` to opt out. If `OTEL_EXPORTER_OTLP_ENDPOINT` is set in the environment, the CLI also exports OpenTelemetry traces and logs to that OTLP HTTP endpoint. {% /tab %} {% tab label="VSCode (Legacy)" %} diff --git a/packages/kilo-docs/pages/getting-started/setup-authentication.md b/packages/kilo-docs/pages/getting-started/setup-authentication.md index 975d456e013..4b364aaf73e 100644 --- a/packages/kilo-docs/pages/getting-started/setup-authentication.md +++ b/packages/kilo-docs/pages/getting-started/setup-authentication.md @@ -12,7 +12,7 @@ When you install Kilo Code, you'll be prompted to sign in or create a free accou {% tabs %} {% tab label="VSCode" %} -The extension prompts you to sign in when you first open the sidebar. Click **Sign In** and complete the browser-based flow. The extension communicates with the CLI backend, so authentication is shared between the CLI and extension. +The extension prompts you to sign in when you first open Kilo Code in VS Code. Click **Sign In** and complete the browser-based flow. Sign-in applies across extension surfaces, including the sidebar and Agent Manager. {% /tab %} {% tab label="CLI" %} @@ -106,13 +106,13 @@ Already have a ChatGPT subscription? You can use it with Kilo Code through the [ {% tabs %} {% tab label="VSCode" %} -1. Open the Kilo Code sidebar in VS Code -2. Click the gear icon ({% codicon name="gear" /%}) to open **Settings** +1. Open Kilo Code in VS Code +2. Click the gear icon ({% codicon name="gear" /%}) in the extension UI to open **Settings** 3. Go to the **Providers** tab 4. Select your provider and enter your API key 5. Choose your model -You can also use `kilo auth login` for providers that support OAuth (like GitHub Copilot). The extension reads from the same underlying config files as the CLI, so provider settings are shared. +These settings apply across the extension, including the sidebar and Agent Manager. Agent Manager does not need separate provider or Kilo CLI authentication setup. {% /tab %} {% tab label="CLI" %} diff --git a/packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md b/packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md index 9c28050cf41..f813ed95674 100644 --- a/packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md +++ b/packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md @@ -47,6 +47,64 @@ Once you have the Developer Tools console open: 3. **Check for errors**: Look at the Console tab for error messages (usually shown in red). If you suspect connection issues, also check the **Network** tab 4. **Copy the logs**: Right-click in the console and select "Save as..." or copy the relevant error messages +## SQLite database is malformed + +If every prompt fails with `SQLiteError: database disk image is malformed`, Kilo Code's local SQLite database may be corrupted. This database stores local Kilo state such as sessions and history. + +### Find the database + +The database location depends on where Kilo Code is running: + +| Environment | Database path | +|---|---| +| Windows | `%LOCALAPPDATA%\kilo\kilo.db` | +| macOS | `~/Library/Application Support/kilo/kilo.db` | +| Linux | `~/.local/share/kilo/kilo.db` | +| VS Code Remote SSH | `~/.local/share/kilo/kilo.db` on the remote machine | + +{% callout type="warning" %} +When using VS Code Remote SSH, check the remote Linux machine, not your local Windows or macOS computer. +{% /callout %} + +### Reset the database + +Close VS Code or stop the Kilo backend first. On Linux or Remote SSH, run: + +```bash +pkill -f "kilo serve" +mkdir -p ~/.local/share/kilo +mv ~/.local/share/kilo/kilo.db ~/.local/share/kilo/kilo.db.bak +mv ~/.local/share/kilo/kilo.db-wal ~/.local/share/kilo/kilo.db-wal.bak 2>/dev/null +mv ~/.local/share/kilo/kilo.db-shm ~/.local/share/kilo/kilo.db-shm.bak 2>/dev/null +``` + +Then reload VS Code or reconnect Remote SSH. Kilo Code recreates the database the next time it starts. + +On Windows or macOS, rename the database file and any `kilo.db-wal` or `kilo.db-shm` files in the same folder, then restart the IDE. + +{% callout type="warning" %} +Renaming this database resets local Kilo Code sessions and history for that machine. Keep the `.bak` files if you need to share them with support or attempt recovery later. +{% /callout %} + +### Fully reset local Kilo data + +If resetting the database does not fix the issue, you can fully reset Kilo Code's local data. This also removes local configuration and cache files, so use it only after trying the database reset above. + +On Linux or VS Code Remote SSH, run this on the machine where Kilo Code is running: + +```bash +pkill -f "kilo serve" +mv ~/.local/share/kilo ~/.local/share/kilo.bak 2>/dev/null +mv ~/.config/kilo ~/.config/kilo.bak 2>/dev/null +mv ~/.cache/kilo ~/.cache/kilo.bak 2>/dev/null +``` + +Then reload VS Code or reconnect Remote SSH. Kilo Code recreates these directories the next time it starts. + +{% callout type="warning" %} +This resets local sessions, history, settings, and cached data. Prefer renaming the directories instead of deleting them so you can recover files. Remove secrets such as API keys or tokens before sharing any backup with support. +{% /callout %} + ## Contact Support If you're unable to resolve the issue, please inspect the console logs, remove any secrets, and send the logs to **[hi@kilocode.ai](mailto:hi@kilocode.ai)** along with the following: diff --git a/packages/kilo-docs/pages/index.tsx b/packages/kilo-docs/pages/index.tsx index b3f89322e91..61452757875 100644 --- a/packages/kilo-docs/pages/index.tsx +++ b/packages/kilo-docs/pages/index.tsx @@ -65,6 +65,25 @@ const terminalContent = { $ kilo rules list ), + kiloclaw: ( + <> + # Chat with your KiloClaw agent from the CLI + {"\n"} + $ kilo /claw + {"\n"} + {"\n"} + # Or connect via chat platforms + {"\n"} + # Telegram, Discord, Slack — no self-hosting required + {"\n"} + {"\n"} + # Trigger your agent via webhook + {"\n"} + $ curl -X POST https://your-instance.kiloclaw.ai/webhook \{"\n"} + -H "Content-Type: application/json" \{"\n"} + {` -d '{"event":"deploy","repo":"my-app"}'`} + + ), } // Category card data based on the information architecture @@ -100,6 +119,24 @@ const categories = [ { title: "Custom Rules", href: "/code-with-ai" }, ], }, + { + title: "KiloClaw", + description: + "Hosted OpenClaw agents — deploy, manage, and integrate AI agents with chat platforms, dev tools, and triggers without self-hosting.", + href: "/kiloclaw/overview", + icon: ( + + + + + + ), + links: [ + { title: "Overview", href: "/kiloclaw/overview" }, + { title: "Chat Platforms", href: "/kiloclaw/chat-platforms" }, + { title: "Development Tools", href: "/kiloclaw/development-tools" }, + ], + }, { title: "Collaborate", description: "Work with teams, share sessions, and manage organizations", @@ -193,7 +230,9 @@ const categories = [ ] export default function HomePage() { - const [activeTab, setActiveTab] = useState<"installation" | "firstTask" | "customRules" | "gateway">("installation") + const [activeTab, setActiveTab] = useState<"installation" | "firstTask" | "customRules" | "gateway" | "kiloclaw">( + "installation", + ) return (
@@ -203,16 +242,17 @@ export default function HomePage() { {/* Hero Section */}
-

Kilo Code Documentation

+

Kilo Documentation

- Explore our guides and examples to build with Kilo Code, the most popular open source coding agent. + Explore guides and examples for the Kilo platform — from coding agents and AI-powered development to hosted + agentic infrastructure with KiloClaw.

Get started with Kilo Code → - - Explore all features + + Explore KiloClaw
@@ -220,7 +260,7 @@ export default function HomePage() { {/* Quick Links Panel - Stripe style */}
-

POPULAR GUIDES

+

KILO CODE

Installation Guide @@ -233,6 +273,20 @@ export default function HomePage() {
+
+

KILOCLAW

+
+ + Overview + + + Chat Platforms + + + Development Tools + +
+

PLATFORMS

@@ -250,10 +304,10 @@ export default function HomePage() {
- {/* Browse by Category - Three Column Layout */} + {/* Browse by Category - Four Column Layout */}
- {categories.slice(0, 3).map((category) => ( + {categories.slice(0, 4).map((category) => (

{category.title}

@@ -291,7 +345,7 @@ export default function HomePage() {

Try it out

-

Get started quickly with common Kilo Code commands

+

Get started quickly with Kilo Code, KiloClaw, and Kilo Gateway

@@ -313,6 +367,12 @@ export default function HomePage() { > Custom Rules + \n}\n", + after: + "import { createSignal } from 'solid-js'\n\nexport function Counter() {\n const [count, setCount] = createSignal(1)\n return \n}\n", + additions: 1, + deletions: 1, + }, + }, + time: { start: now - 4000, end: now - 3500 }, + }, +} + // --- Reasoning part --- const reasoningPart: ReasoningPart = { @@ -208,10 +241,12 @@ const mockDataReasoning = createMockData([reasoningPart, textPart]) const mockDataBash = createMockData([bashCompleted]) // Three context-group tools — exercises ContextToolGroupHeader collapse path const mockDataContextGroup = createMockData([completedToolPart, grepCompleted, globCompleted, textPart]) +// Completed edit tool with filediff — exercises the "Open in Diff Viewer" button path +const mockDataEdit = createMockData([editCompletedPart]) -function AllProviders(props: { children: any; data?: MockData }) { +function AllProviders(props: { children: any; data?: MockData; onOpenDiff?: () => void }) { return ( - + @@ -354,6 +389,25 @@ export const WithContextGroup: Story = { ), } +// --- Completed edit tool with filediff → "Open in Diff Viewer" icon visible --- +// +// CSS :hover cannot be triggered reliably from Storybook `play` functions, +// so the action slot is force-revealed via a scoped style override. This +// captures the layout with the icon present so the visual regression +// suite catches any regression in: +// - the `:has()` parent-grow rule on basic-tool-tool-info +// - the `margin-left: auto` pushing the action to the right +// - the icon-button ghost variant in the tool-trigger row +export const WithEditToolOpenDiffAction: Story = { + name: "WithEditTool (open-diff action visible)", + render: () => ( + {}}> + + + + ), +} + // --- All 5 tool hint error types in a single screenshot --- const hintErrors: ToolPart[] = [ diff --git a/packages/kilo-vscode/CHANGELOG.md b/packages/kilo-vscode/CHANGELOG.md index d3e2e0d3ea6..9765fc11db5 100644 --- a/packages/kilo-vscode/CHANGELOG.md +++ b/packages/kilo-vscode/CHANGELOG.md @@ -1,5 +1,39 @@ # kilo-code +## 7.2.33 + +### Minor Changes + +- [#9737](https://github.com/Kilo-Org/kilocode/pull/9737) [`d5fb9eb`](https://github.com/Kilo-Org/kilocode/commit/d5fb9eb2265c03127e776c99020b03bb770255a1) - Support starting Agent Manager local sessions and worktree sessions from an experimental agent tool. + +- [#9704](https://github.com/Kilo-Org/kilocode/pull/9704) [`22b5283`](https://github.com/Kilo-Org/kilocode/commit/22b5283f79e4dd8461764904b908001df969cce9) - Keep reasoning blocks expanded by default and add a setting to auto-collapse them after completion. + +- [#9708](https://github.com/Kilo-Org/kilocode/pull/9708) [`f2db4d1`](https://github.com/Kilo-Org/kilocode/commit/f2db4d165cacc8b52148e7fe77fd4af7ff71b7bc) - Add a Display setting to collapse terminal command blocks by default instead of keeping them expanded. + +### Patch Changes + +- [#9317](https://github.com/Kilo-Org/kilocode/pull/9317) [`ce4a595`](https://github.com/Kilo-Org/kilocode/commit/ce4a595cdcba5074b0ebdf74f24213a8322f2cc7) - Dispose autocomplete editor listeners when inline completions are disabled. + +- [#9651](https://github.com/Kilo-Org/kilocode/pull/9651) [`bc91af3`](https://github.com/Kilo-Org/kilocode/commit/bc91af398db6e52e4a6bbd1d26efce1b306d4697) - Center the sidebar prompt toolbar on wider sidebars and hide unavailable toolbar actions. + +- [#9753](https://github.com/Kilo-Org/kilocode/pull/9753) [`fb11c7b`](https://github.com/Kilo-Org/kilocode/commit/fb11c7b56d49cdd52f8ee96fdfc9ce30d2c883b7) - Center welcome notifications in wide VS Code views. + +- [#9460](https://github.com/Kilo-Org/kilocode/pull/9460) [`26e4c11`](https://github.com/Kilo-Org/kilocode/commit/26e4c1148f4e7a734bb8e535e02a1a9ad75be584) - Scope the custom commit message prompt to the current project. Setting it in the VS Code settings now writes to the workspace's `kilo.json` so different repositories can have different conventions, instead of silently applying globally. Also fixes the project-level config update endpoint, which previously wrote to a file that wasn't loaded. + +- [#9742](https://github.com/Kilo-Org/kilocode/pull/9742) [`a076e33`](https://github.com/Kilo-Org/kilocode/commit/a076e336b34f897a6b9463b48d2181d90f9dd997) - Preserve Agent Manager local sessions across panel restarts when session refreshes complete out of order. + +- [#9733](https://github.com/Kilo-Org/kilocode/pull/9733) [`e9f2760`](https://github.com/Kilo-Org/kilocode/commit/e9f2760d8e9a93d4b71dba6ef5aae522cb4fe263) - Support configuring custom agent tool permissions from the VS Code agent editor. + +- [#9669](https://github.com/Kilo-Org/kilocode/pull/9669) [`0bf14eb`](https://github.com/Kilo-Org/kilocode/commit/0bf14eb2ff5ef59f9dc98342218addc670a87481) - Stop emitting `ai.*` and `gen_ai.*` OpenTelemetry spans from AI SDK calls, and remove the PostHog bridge that forwarded them. Tool/session/indexing telemetry is unchanged. + +## 7.2.31 + +### Patch Changes + +- [#9687](https://github.com/Kilo-Org/kilocode/pull/9687) [`9028174`](https://github.com/Kilo-Org/kilocode/commit/9028174cfd5fdd0cf2f3dd87d5ace7cfa780cc4d) - Show compact todo update cards when checking off items in long todo lists. + +- [#9691](https://github.com/Kilo-Org/kilocode/pull/9691) [`ae726cd`](https://github.com/Kilo-Org/kilocode/commit/ae726cdc45317a6bc6d2a77066c191dd674d0aa2) - Show apply_patch diffs in the permission prompt before approval. + ## 7.2.30 ### Patch Changes diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 968ab4b14f5..aa7acdcca2b 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -2,7 +2,7 @@ "name": "kilo-code", "displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete", "description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.", - "version": "7.2.30", + "version": "7.2.34", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/packages/kilo-vscode/script/local-bin.ts b/packages/kilo-vscode/script/local-bin.ts index ee31c76a666..aeb57e81643 100644 --- a/packages/kilo-vscode/script/local-bin.ts +++ b/packages/kilo-vscode/script/local-bin.ts @@ -20,6 +20,7 @@ const forceRebuild = process.argv.includes("--force") const kiloVscodeDir = join(import.meta.dir, "..") const packagesDir = join(kiloVscodeDir, "..") const opencodeDir = join(packagesDir, "opencode") +const coreDir = join(packagesDir, "core") const targetBinDir = join(kiloVscodeDir, "bin") const binName = process.platform === "win32" ? "kilo.exe" : "kilo" @@ -32,8 +33,9 @@ function log(msg: string) { async function cliSourceHash(): Promise { try { - const result = await $`git log -1 --format=%H -- .`.cwd(opencodeDir).quiet() - return result.text().trim() || null + const opencodeResult = await $`git log -1 --format=%H -- .`.cwd(opencodeDir).quiet() + const coreResult = await $`git log -1 --format=%H -- .`.cwd(coreDir).quiet() + return `${opencodeResult.text().trim()}-${coreResult.text().trim()}` || null } catch { return null } @@ -41,8 +43,9 @@ async function cliSourceHash(): Promise { async function isDirty(): Promise { try { - const result = await $`git status --porcelain -- .`.cwd(opencodeDir).quiet() - return result.text().trim().length > 0 + const opencodeResult = await $`git status --porcelain -- .`.cwd(opencodeDir).quiet() + const coreResult = await $`git status --porcelain -- .`.cwd(coreDir).quiet() + return opencodeResult.text().trim().length > 0 || coreResult.text().trim().length > 0 } catch { return false } @@ -119,8 +122,8 @@ async function ensureBuiltBinary(): Promise { `No prebuilt binary found under ${relative(kiloVscodeDir, join(opencodeDir, "dist"))} - attempting build via bun.`, ) - const bunFile = Bun.file(await Bun.which("bun")) - if (!(await bunFile.exists())) { + const bunPath = Bun.which("bun") + if (!bunPath) { throw new Error( `Bun is required to build the CLI binary, but was not found on PATH. ` + `Install bun, or build the CLI separately in ${opencodeDir} and re-run.`, diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index bf9d8872c80..3b0ebc44b29 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -827,7 +827,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper ) break case "updateConfig": - await this.handleUpdateConfig(message.config) + await this.handleUpdateConfig(message.config, message.projectConfig) break case "openSettingsTab": if (message.tab === "indexing") { @@ -2303,12 +2303,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return getBusySessionCount(this.sessionStatusMap) } - /** - * Handle config update request from the webview. - * Applies a partial config update via the global config endpoint, then pushes - * the full merged config back to the webview. - */ - private async handleUpdateConfig(partial: Partial): Promise { + private async handleUpdateConfig(partial: Partial, project: Partial = {}): Promise { if (!this.client || this.connectionState !== "connected") { this.postMessage({ type: "configUpdateFailed", message: "Not connected to CLI backend" }) return @@ -2318,40 +2313,34 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper partial.provider !== undefined || partial.disabled_providers !== undefined || partial.enabled_providers !== undefined + const hasGlobal = Object.keys(partial).length > 0 + const hasProject = Object.keys(project).length > 0 - // Guard against fetchAndSendConfig pushing stale data while the write is in flight. this.pending++ + const dir = this.getWorkspaceDirectory() - // Phase 1: write. Errors here = real save failures the user can fix + retry. try { await this.connectionService.drainPendingPrompts() - await this.client.global.config.update({ config: partial }, { throwOnError: true }) + if (hasGlobal) await this.client.global.config.update({ config: partial }, { throwOnError: true }) + if (hasProject) await this.client.config.update({ config: project, directory: dir }, { throwOnError: true }) } catch (error) { - console.error("[Kilo New] KiloProvider: Failed to update config:", error) - this.postMessage({ - type: "configUpdateFailed", - message: getErrorMessage(error) || "Failed to update config", - details: getConfigErrorDetails(error), - }) + this.postConfigFailure(error) this.pending-- return } - // Phase 2: refresh. Config is already on disk — post-write errors are - // transient, so send an optimistic configUpdated to clear the webview's - // saving/draft state. SSE global.config.updated pushes the real data next. try { - const dir = this.getWorkspaceDirectory() const { data: merged } = await retry(() => this.client!.config.get({ directory: dir }, { throwOnError: true })) this.cachedConfigMessage = { type: "configLoaded", config: merged, features: configFeatures(merged) } this.postMessage({ type: "configUpdated", config: merged, features: configFeatures(merged) }) if (refreshProviders) await this.fetchAndSendProviders() } catch (error) { console.error("[Kilo New] KiloProvider: Config write succeeded but post-write refresh failed:", error) + const patch = { ...partial, ...project } const cached = (this.cachedConfigMessage as { config?: unknown } | null)?.config const features = (this.cachedConfigMessage as { features?: unknown } | null)?.features const optimistic = - cached && typeof cached === "object" ? { ...(cached as Record), ...partial } : partial + cached && typeof cached === "object" ? { ...(cached as Record), ...patch } : patch this.postMessage({ type: "configUpdated", config: optimistic, @@ -2362,6 +2351,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + private postConfigFailure(error: unknown): void { + console.error("[Kilo New] KiloProvider: Failed to update config:", error) + this.postMessage({ + type: "configUpdateFailed", + message: getErrorMessage(error) || "Failed to update config", + details: getConfigErrorDetails(error), + }) + } + private async resolveSession(sessionID?: string, draftID?: string, context?: string) { if (!this.client) return undefined diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 788248d17db..2bce7ae94d8 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -29,6 +29,7 @@ import { continueInWorktree } from "./continue-in-worktree" import { WorktreeDiffController } from "./worktree-diff-controller" import { WorktreeImporter } from "./worktree-importer" import { diffSummary as localDiffSummary, diffFile as localDiffFile } from "./local-diff" +import { parseToolRequest, startFromTool, type ToolRequest } from "./tool-start" import { buildKeybindingMap } from "./format-keybinding" import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version" @@ -65,6 +66,7 @@ export class AgentManagerProvider implements Disposable { private staleWorktreeIds = new Set() private cachedWorktreeStats: { type: "agentManager.worktreeStats"; stats: WorktreeStats[] } | undefined private cachedLocalStats: { type: "agentManager.localStats"; stats: LocalStats } | undefined + private unsubTool: (() => void) | undefined /** Session ID most recently loaded via a `loadMessages` message from the webview. * Updated synchronously — unlike the session provider's currentSession which depends on @@ -152,6 +154,10 @@ export class AgentManagerProvider implements Disposable { log: (...a) => this.log(...a), semaphore, }) + this.unsubTool = this.connectionService.onEventFiltered( + (event) => (event as { type?: string }).type === "kilocode.agent_manager.start", + (event, directory) => this.onToolEvent(event, directory), + ) } private log(...args: unknown[]) { @@ -159,11 +165,11 @@ export class AgentManagerProvider implements Disposable { this.outputChannel.appendLine(`${new Date().toISOString()} ${msg}`) } - public openPanel(): void { + public openPanel(preserveFocus?: boolean): void { if (this.panel) { this.log("Panel already open, revealing") - this.panel.reveal() - this.postToWebview({ type: "action", action: "focusInput" }) + this.panel.reveal(preserveFocus) + if (!preserveFocus) this.postToWebview({ type: "action", action: "focusInput" }) return } this.log("Opening Agent Manager panel") @@ -792,6 +798,43 @@ export class AgentManagerProvider implements Disposable { await this.stateReady.catch((err) => this.log(`${context}: stateReady rejected, continuing:`, err)) } + private onToolEvent(event: unknown, directory?: string): void { + const properties = (event as { properties?: unknown }).properties + const req = parseToolRequest(properties) + if (!req) return + if (directory) req.directory = directory + void this.startToolRequest(req) + } + + private async startToolRequest(req: ToolRequest): Promise { + await startFromTool( + { + getClient: () => this.connectionService.getClient(), + getRoot: () => this.getRoot(), + getState: () => this.getStateManager(), + getPanel: () => this.panel, + openPanel: (preserveFocus) => this.openPanel(preserveFocus), + waitReady: (context) => this.waitForStateReady(context), + createWorktree: (opts) => this.createWorktreeOnDisk(opts), + cleanupWorktree: async (wid, dir) => { + this.getStateManager()?.removeWorktree(wid) + await this.getWorktreeManager()?.removeWorktree(dir) + this.pushState() + }, + setup: (dir, branch, id) => this.runSetupScriptForWorktree(dir, branch, id), + createSessionInWorktree: (dir, branch, id) => this.createSessionInWorktree(dir, branch, id), + registerWorktreeSession: (sid, dir) => this.registerWorktreeSession(sid, dir), + notifyReady: (sid, result, wid) => this.notifyWorktreeReady(sid, result, wid), + push: () => this.pushState(), + post: (msg) => this.postToWebview(msg as AgentManagerOutMessage), + capture: (event, props) => this.host.capture(event, props), + log: (...args) => this.log(...args), + error: (msg) => this.host.showError(msg), + }, + req, + ) + } + // --------------------------------------------------------------------------- // Worktree actions // --------------------------------------------------------------------------- @@ -1565,6 +1608,7 @@ export class AgentManagerProvider implements Disposable { } public dispose(): void { + this.unsubTool?.() this.connectionService.unregisterFocused("agent-manager") this.connectionService.registerOpen("agent-manager", []) this.diffs.stop() diff --git a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts index 64224d16137..01ec67fa7ff 100644 --- a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts +++ b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts @@ -7,8 +7,12 @@ export const SETUP_SCRIPT_TEMPLATE = `#!/bin/sh # WORKTREE_PATH - Absolute path to the worktree directory # REPO_PATH - Absolute path to the main repository # +# Kilo already copies root-level .env and .env.* files before this script runs. +# Use this script for dependencies, nested env files, local config, databases, +# certificates, or other project-specific setup that is not committed to git. +# # Example tasks: -# - Copy .env files from main repo +# - Copy nested env files from main repo # - Install dependencies # - Run database migrations # - Set up local configuration @@ -19,10 +23,10 @@ echo "Setting up worktree: $WORKTREE_PATH" # Uncomment and modify as needed: -# Copy environment files -# if [ -f "$REPO_PATH/.env" ]; then -# cp "$REPO_PATH/.env" "$WORKTREE_PATH/.env" -# echo "Copied .env" +# Copy a nested environment file +# if [ -f "$REPO_PATH/apps/web/.env.local" ] && [ ! -f "$WORKTREE_PATH/apps/web/.env.local" ]; then +# cp "$REPO_PATH/apps/web/.env.local" "$WORKTREE_PATH/apps/web/.env.local" +# echo "Copied apps/web/.env.local" # fi # Install dependencies (Node.js) @@ -48,8 +52,12 @@ export const SETUP_SCRIPT_TEMPLATE_POWERSHELL = `# Kilo Code Worktree Setup Scri # $env:WORKTREE_PATH - Absolute path to the worktree directory # $env:REPO_PATH - Absolute path to the main repository # +# Kilo already copies root-level .env and .env.* files before this script runs. +# Use this script for dependencies, nested env files, local config, databases, +# certificates, or other project-specific setup that is not committed to git. +# # Example tasks: -# - Copy .env files from main repo +# - Copy nested env files from main repo # - Install dependencies # - Run database migrations # - Set up local configuration @@ -60,10 +68,10 @@ Write-Host "Setting up worktree: $env:WORKTREE_PATH" # Uncomment and modify as needed: -# Copy environment files -# if (Test-Path "$env:REPO_PATH/.env") { -# Copy-Item "$env:REPO_PATH/.env" "$env:WORKTREE_PATH/.env" -Force -# Write-Host "Copied .env" +# Copy a nested environment file +# if ((Test-Path "$env:REPO_PATH/apps/web/.env.local") -and !(Test-Path "$env:WORKTREE_PATH/apps/web/.env.local")) { +# Copy-Item "$env:REPO_PATH/apps/web/.env.local" "$env:WORKTREE_PATH/apps/web/.env.local" +# Write-Host "Copied apps/web/.env.local" # } # Install dependencies (Node.js) diff --git a/packages/kilo-vscode/src/agent-manager/tool-start.ts b/packages/kilo-vscode/src/agent-manager/tool-start.ts new file mode 100644 index 00000000000..51b9b0bdcb4 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/tool-start.ts @@ -0,0 +1,257 @@ +import type { KiloClient, Session } from "@kilocode/sdk/v2/client" +import { sanitizeBranchName, versionedName } from "./branch-name" +import type { CreateWorktreeResult } from "./WorktreeManager" +import type { WorktreeStateManager } from "./WorktreeStateManager" +import type { PanelContext } from "./host" +import { PLATFORM } from "./constants" + +const LABEL_MAX = 28 +const PREFIX = new Set(["feat", "fix", "chore", "bug", "issue", "task", "branch"]) + +export interface ToolTask { + prompt?: string + name?: string + branchName?: string +} + +export interface ToolRequest { + requestID: string + sessionID?: string + directory?: string + mode: "worktree" | "local" + versions?: boolean + tasks: ToolTask[] +} + +interface WorktreeCreated { + worktree: ReturnType + result: CreateWorktreeResult +} + +export interface ToolDeps { + getClient: () => KiloClient + getRoot: () => string | undefined + getState: () => WorktreeStateManager | undefined + getPanel: () => PanelContext | undefined + openPanel: (preserveFocus?: boolean) => void + waitReady: (context: string) => Promise + createWorktree: (opts: { + groupId?: string + branchName?: string + name?: string + label?: string + }) => Promise + cleanupWorktree: (wid: string, dir: string) => Promise + setup: (dir: string, branch?: string, id?: string) => Promise + createSessionInWorktree: (dir: string, branch: string, id?: string) => Promise + registerWorktreeSession: (sid: string, dir: string) => void + notifyReady: (sid: string, result: CreateWorktreeResult, wid?: string) => void + push: () => void + post: (msg: unknown) => void + capture: (event: string, props?: Record) => void + log: (...args: unknown[]) => void + error: (msg: string) => void +} + +function text(task: ToolTask): string | undefined { + return task.prompt?.trim() || undefined +} + +function clean(value: string | undefined): string | undefined { + return value?.trim() || undefined +} + +function label(value: string | undefined): string | undefined { + const raw = clean(value) + if (!raw) return undefined + const words = raw + .toLowerCase() + .replace(/[/_.-]+/g, " ") + .replace(/[^a-z0-9 ]+/g, "") + .replace(/\s+/g, " ") + .trim() + .split(" ") + .filter(Boolean) + const meaningful = words.filter((word) => !PREFIX.has(word)) + const picked: string[] = [] + for (const word of meaningful.length > 0 ? meaningful : words) { + const next = [...picked, word].join(" ") + if (next.length > LABEL_MAX) break + picked.push(word) + if (picked.length >= 3) break + } + return picked.join(" ") || words[0]?.slice(0, LABEL_MAX) || undefined +} + +function branch(value: string | undefined): string | undefined { + const raw = clean(value) + if (!raw) return undefined + return sanitizeBranchName(raw) || undefined +} + +function versionedLabel(base: string | undefined, index: number, total: number): string | undefined { + if (!base) return undefined + if (total > 1 && index > 0) return `${base} v${index + 1}` + return base +} + +async function prompt(client: KiloClient, sid: string, dir: string, task: ToolTask) { + const body = text(task) + if (!body) return + await client.session.promptAsync( + { + sessionID: sid, + directory: dir, + parts: [{ type: "text", text: body }], + }, + { throwOnError: true }, + ) +} + +async function local(deps: ToolDeps, client: KiloClient, task: ToolTask, directory?: string) { + const root = deps.getRoot() + const state = deps.getState() + if (!root || !state) return false + + const dir = clean(directory) ?? root + const wt = dir === root ? undefined : state.findWorktreeByPath(dir) + if (dir !== root && !wt) { + deps.log("Agent Manager tool local request ignored unknown directory", dir) + deps.post({ + type: "error", + message: `Agent Manager tool cannot start a local session for unknown directory: ${dir}`, + }) + return false + } + const target = wt?.path ?? root + const { data } = await client.session.create({ directory: target, platform: PLATFORM }, { throwOnError: true }) + const session = data + state.addSession(session.id, wt?.id ?? null) + if (wt) deps.registerWorktreeSession(session.id, wt.path) + deps.push() + deps.getPanel()?.sessions.registerSession(session) + if (wt) deps.post({ type: "agentManager.sessionAdded", sessionId: session.id, worktreeId: wt.id }) + await prompt(client, session.id, target, task) + deps.capture("Agent Manager Session Started", { + source: PLATFORM, + sessionId: session.id, + tool: true, + mode: "local", + worktreeId: wt?.id, + }) + return true +} + +async function worktree( + deps: ToolDeps, + client: KiloClient, + task: ToolTask, + index: number, + total: number, + groupId?: string, + versions?: boolean, +) { + const baseBranch = branch(task.branchName) ?? branch(task.name) + const baseLabel = label(task.name) ?? label(task.branchName) ?? label(task.prompt) + const version = versionedName(baseBranch, versions ? index : 0, versions ? total : 1) + const created = await deps.createWorktree({ + groupId, + branchName: version.branch, + name: version.branch, + label: versionedLabel(baseLabel, versions ? index : 0, versions ? total : 1), + }) + if (!created) return false + + await deps.setup(created.result.path, created.result.branch, created.worktree.id) + const session = await deps.createSessionInWorktree(created.result.path, created.result.branch, created.worktree.id) + if (!session) { + await deps.cleanupWorktree(created.worktree.id, created.result.path) + return false + } + + const state = deps.getState() + if (!state) { + await deps.cleanupWorktree(created.worktree.id, created.result.path) + return false + } + state.addSession(session.id, created.worktree.id) + deps.registerWorktreeSession(session.id, created.result.path) + deps.notifyReady(session.id, created.result, created.worktree.id) + deps.getPanel()?.sessions.registerSession(session) + await prompt(client, session.id, created.result.path, task) + deps.capture("Agent Manager Session Started", { + source: PLATFORM, + sessionId: session.id, + worktreeId: created.worktree.id, + branch: created.result.branch, + tool: true, + }) + return true +} + +export async function startFromTool(deps: ToolDeps, req: ToolRequest): Promise { + deps.openPanel(true) + await deps.getPanel()?.waitForReady() + await deps.waitReady("startFromTool") + const client = deps.getClient() + const total = req.tasks.length + const versions = req.mode === "worktree" && req.versions === true && total > 1 + const groupId = versions ? `grp-${Date.now()}` : undefined + const state = { ok: 0 } + + deps.post({ type: "agentManager.multiVersionProgress", status: "creating", total, completed: 0, groupId }) + for (let i = 0; i < req.tasks.length; i++) { + const task = req.tasks[i]! + try { + const done = + req.mode === "local" + ? await local(deps, client, task, req.directory) + : await worktree(deps, client, task, i, total, groupId, versions) + if (done) state.ok++ + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + deps.log("Agent Manager tool task failed", msg) + deps.post({ type: "error", message: `Agent Manager tool task failed: ${msg}` }) + } + deps.post({ type: "agentManager.multiVersionProgress", status: "creating", total, completed: state.ok, groupId }) + } + + deps.post({ type: "agentManager.multiVersionProgress", status: "done", total, completed: state.ok, groupId }) + if (state.ok === 0) deps.error(`Failed to start any Agent Manager sessions for request ${req.requestID}.`) + deps.log(`Agent Manager tool request ${req.requestID} complete: ${state.ok}/${total}`) +} + +function record(value: unknown): value is Record { + return !!value && typeof value === "object" +} + +function task(value: unknown): ToolTask | undefined { + if (!record(value)) return undefined + const out: ToolTask = {} + for (const key of ["prompt", "name", "branchName"] as const) { + if (typeof value[key] === "string" && value[key].trim()) out[key] = value[key] + } + if (!out.prompt && !out.name && !out.branchName) return undefined + return out +} + +export function parseToolRequest(value: unknown): ToolRequest | undefined { + if (!record(value)) return undefined + const mode = value.mode + const tasks = value.tasks + if (mode !== "worktree" && mode !== "local") return undefined + if (!Array.isArray(tasks) || tasks.length === 0) return undefined + const parsed = tasks + .slice(0, 20) + .map(task) + .filter((item): item is ToolTask => !!item) + if (parsed.length === 0) return undefined + return { + requestID: typeof value.requestID === "string" ? value.requestID : `am-${Date.now()}`, + sessionID: typeof value.sessionID === "string" ? value.sessionID : undefined, + directory: typeof value.directory === "string" ? value.directory : undefined, + mode, + versions: typeof value.versions === "boolean" ? value.versions : undefined, + tasks: parsed, + } +} diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts index 2e2c865dffa..01e51ac0095 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts @@ -133,6 +133,7 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple /** Abort controller for the current in-flight FIM request */ private fimAbortController: AbortController | null = null private acceptedCommand: vscode.Disposable | null = null + private contextService: ContextRetrievalService | null = null private debounceDelayMs: number = INITIAL_DEBOUNCE_DELAY_MS private latencyHistory: number[] = [] private telemetry: AutocompleteTelemetry | null @@ -167,10 +168,10 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple })() const ide = new VsCodeIde(context) - const contextService = new ContextRetrievalService(ide) + this.contextService = new ContextRetrievalService(ide) const contextProvider: AutocompleteContextProvider = { ide, - contextService, + contextService: this.contextService, model, ignoreController: this.ignoreController, } @@ -304,6 +305,8 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple this.fimAbortController?.abort() this.fimAbortController = null this.telemetry?.dispose() + this.contextService?.dispose() + this.contextService = null this.recentlyVisitedRangesService.dispose() this.recentlyEditedTracker.dispose() void this.disposeIgnoreController() diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts index 8b6e8ae5a75..fe4a917f961 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts @@ -64,6 +64,10 @@ export class ContextRetrievalService { return this.staticContextService.getContext(helper) } + public dispose(): void { + this.importDefinitionsService.dispose() + } + /** * Initialize the import definitions cache for a file. * This is normally done automatically when the active text editor changes, diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts index 5e24cfe2713..71fafe82e72 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts @@ -25,6 +25,16 @@ describe("ImportDefinitionsService", () => { service = new ImportDefinitionsService(mockIde) }) + it("should dispose active editor listener", () => { + const disposable = { dispose: vi.fn() } + mockIde.onDidChangeActiveTextEditor = vi.fn().mockReturnValue(disposable) + service = new ImportDefinitionsService(mockIde) + + service.dispose() + + expect(disposable.dispose).toHaveBeenCalledTimes(1) + }) + describe("Python Imports - Real Tree-sitter Parsing", () => { it("should extract named imports from 'from module import' statements", async () => { const pythonCode = `from os.path import join, exists diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts index 538d79d218f..523af450f6c 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts @@ -1,4 +1,4 @@ -import { IDE, RangeInFileWithContents } from "../.." +import { Disposable, IDE, RangeInFileWithContents } from "../.." import { PrecalculatedLruCache } from "../../util/LruCache" import { getFullLanguageName, getParserForFile, getQueryForFile } from "../../util/treeSitter" import { findUriInDirs } from "../../util/uri" @@ -14,15 +14,20 @@ export class ImportDefinitionsService { this._getFileInfo.bind(this), ImportDefinitionsService.N, ) + private readonly disposable: Disposable | void constructor(private readonly ide: IDE) { - ide.onDidChangeActiveTextEditor((filepath) => { + this.disposable = ide.onDidChangeActiveTextEditor((filepath) => { this.cache .initKey(filepath) .catch((e) => console.warn(`Failed to initialize ImportDefinitionService: ${e.message}`)) }) } + dispose(): void { + this.disposable?.dispose() + } + get(filepath: string): FileInfo | undefined { return this.cache.get(filepath) } diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts index 014efae8599..5585fe8a087 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts @@ -383,6 +383,10 @@ export type FileStatsMap = { [path: string]: FileStats } +export interface Disposable { + dispose(): void +} + export interface IDE { getIdeInfo(): Promise @@ -423,7 +427,7 @@ export interface IDE { getDocumentSymbols(textDocumentIdentifier: string): Promise // Callbacks - onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void + onDidChangeActiveTextEditor(callback: (fileUri: string) => void): Disposable | void } export type ContextProviderName = | "diff" diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts index 894136e8aab..d3cb779748b 100644 --- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts +++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts @@ -237,8 +237,8 @@ export class VsCodeIde implements IDE { return result } - onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void { - vscode.window.onDidChangeActiveTextEditor((editor) => { + onDidChangeActiveTextEditor(callback: (fileUri: string) => void): vscode.Disposable { + return vscode.window.onDidChangeActiveTextEditor((editor) => { if (editor) { callback(editor.document.uri.toString()) } diff --git a/packages/kilo-vscode/src/services/cli-backend/retry.ts b/packages/kilo-vscode/src/services/cli-backend/retry.ts index 2d901fcc4eb..8895e1afbe6 100644 --- a/packages/kilo-vscode/src/services/cli-backend/retry.ts +++ b/packages/kilo-vscode/src/services/cli-backend/retry.ts @@ -1,4 +1,4 @@ -// Replicated from packages/shared/src/util/retry.ts to avoid adding @opencode-ai/shared +// Replicated from packages/core/src/util/retry.ts to avoid adding @opencode-ai/core // as a dependency of the extension. Keep in sync with the original. const TRANSIENT = [ diff --git a/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts new file mode 100644 index 00000000000..8742ab2776a --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts @@ -0,0 +1,183 @@ +import { describe, expect, it, mock } from "bun:test" +import { parseToolRequest, startFromTool, type ToolDeps, type ToolRequest } from "../../src/agent-manager/tool-start" +import type { CreateWorktreeResult } from "../../src/agent-manager/WorktreeManager" +import type { Session } from "@kilocode/sdk/v2/client" + +function session(id: string): Session { + return { id, title: id, createdAt: "", updatedAt: "" } as Session +} + +function result(path: string): CreateWorktreeResult { + return { path, branch: "kilo/test", parentBranch: "main", startPointSource: "fallback" } as CreateWorktreeResult +} + +function deps(overrides: Partial = {}): ToolDeps { + const calls: unknown[] = [] + const panel = { + waitForReady: mock(async () => calls.push("waitForReady")), + sessions: { registerSession: mock(() => calls.push("registerSession")) }, + } + return { + getClient: () => + ({ + session: { + create: mock(async () => ({ data: session("s-local") })), + promptAsync: mock(async () => ({})), + }, + }) as never, + getRoot: () => "/repo", + getState: () => ({ addSession: mock(() => calls.push("addSession")) }) as never, + getPanel: () => panel as never, + openPanel: mock(() => calls.push("openPanel")), + waitReady: mock(async () => calls.push("waitReady")), + createWorktree: mock(async () => ({ worktree: { id: "wt-1" }, result: result("/repo/.kilo/worktrees/wt-1") })), + cleanupWorktree: mock(async () => calls.push("cleanupWorktree")), + setup: mock(async () => calls.push("setup")), + createSessionInWorktree: mock(async () => session("s-wt")), + registerWorktreeSession: mock(() => calls.push("registerWorktreeSession")), + notifyReady: mock(() => calls.push("notifyReady")), + push: mock(() => calls.push("push")), + post: mock((msg: unknown) => calls.push(msg)), + capture: mock(() => calls.push("capture")), + log: mock(() => {}), + error: mock(() => {}), + ...overrides, + } +} + +describe("agent manager tool start", () => { + it("parses tool start events defensively", () => { + const parsed = parseToolRequest({ mode: "local", tasks: [{ prompt: "one" }] }) + expect(parsed?.requestID.startsWith("am-")).toBe(true) + expect(parsed?.sessionID).toBeUndefined() + expect(parsed?.directory).toBeUndefined() + expect(parsed?.mode).toBe("local") + expect(parsed?.versions).toBeUndefined() + expect(parsed?.tasks).toEqual([{ prompt: "one" }]) + expect(parseToolRequest({ mode: "bad", tasks: [{ prompt: "one" }] })).toBeUndefined() + expect(parseToolRequest({ mode: "local", tasks: [] })).toBeUndefined() + expect(parseToolRequest({ mode: "local", tasks: [{}] })).toBeUndefined() + }) + + it("starts local sessions and sends the initial prompt", async () => { + const client = { + session: { + create: mock(async () => ({ data: session("s-local") })), + promptAsync: mock(async () => ({})), + }, + } + const c = deps({ getClient: () => client as never }) + const req: ToolRequest = { + requestID: "am-1", + mode: "local", + tasks: [{ prompt: "Do work" }], + } + + await startFromTool(c, req) + + expect(c.openPanel).toHaveBeenCalledWith(true) + const panel = c.getPanel() + expect(panel?.waitForReady).toHaveBeenCalled() + expect(client.session.create).toHaveBeenCalledWith( + { directory: "/repo", platform: "agent-manager" }, + { throwOnError: true }, + ) + expect(client.session.promptAsync).toHaveBeenCalledWith( + expect.objectContaining({ + sessionID: "s-local", + directory: "/repo", + parts: [{ type: "text", text: "Do work" }], + }), + { throwOnError: true }, + ) + }) + + it("starts worktree sessions through existing hooks", async () => { + const c = deps() + await startFromTool(c, { requestID: "am-2", mode: "worktree", tasks: [{ prompt: "Fix", branchName: "fix/one" }] }) + + expect(c.createWorktree).toHaveBeenCalledWith( + expect.objectContaining({ branchName: "fix-one", name: "fix-one", label: "one" }), + ) + expect(c.setup).toHaveBeenCalled() + expect(c.createSessionInWorktree).toHaveBeenCalled() + expect(c.registerWorktreeSession).toHaveBeenCalledWith("s-wt", "/repo/.kilo/worktrees/wt-1") + expect(c.notifyReady).toHaveBeenCalled() + }) + + it("only applies version suffixes when versions is true", async () => { + const normal = deps() + await startFromTool(normal, { + requestID: "am-normal", + mode: "worktree", + tasks: [ + { prompt: "Fix one", branchName: "fix/one" }, + { prompt: "Fix two", branchName: "fix/two" }, + ], + }) + expect(normal.createWorktree).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ branchName: "fix-two", label: "two" }), + ) + + const grouped = deps() + await startFromTool(grouped, { + requestID: "am-versions", + mode: "worktree", + versions: true, + tasks: [ + { prompt: "Try one", branchName: "try/work" }, + { prompt: "Try two", branchName: "try/work" }, + ], + }) + expect(grouped.createWorktree).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ branchName: "try-work_v2", label: "try work v2" }), + ) + }) + + it("sanitizes branch names and keeps card labels short", async () => { + const c = deps() + await startFromTool(c, { + requestID: "am-name", + mode: "worktree", + tasks: [ + { + prompt: "Fix command permissions persistence regression", + name: "Fix command permissions persistence regression that is too long", + branchName: "fix command permissions @#$ persistence", + }, + ], + }) + + expect(c.createWorktree).toHaveBeenCalledWith( + expect.objectContaining({ + branchName: "fix-command-permissions-persistence", + label: "command permissions", + }), + ) + }) + + it("rejects local sessions for unknown worktree directories", async () => { + const client = { + session: { + create: mock(async () => ({ data: session("s-local") })), + promptAsync: mock(async () => ({})), + }, + } + const c = deps({ + getClient: () => client as never, + getState: () => ({ addSession: mock(), findWorktreeByPath: mock(() => undefined) }) as never, + }) + + await startFromTool(c, { + requestID: "am-dir", + mode: "local", + directory: "/repo/other", + tasks: [{ prompt: "Do work" }], + }) + + expect(client.session.create).not.toHaveBeenCalled() + expect(c.error).toHaveBeenCalled() + }) +}) diff --git a/packages/kilo-vscode/tests/unit/config-utils.test.ts b/packages/kilo-vscode/tests/unit/config-utils.test.ts index 15ae52b443e..16fba3a660d 100644 --- a/packages/kilo-vscode/tests/unit/config-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/config-utils.test.ts @@ -204,4 +204,72 @@ describe("ConfigState", () => { expect(Object.keys(s.draft).length).toBe(0) }) }) + + describe("agent permission patches", () => { + it("merges nested per-agent permission patches into existing rules", () => { + const s = new ConfigState() + s.handleConfigLoaded({ + agent: { + reviewer: { + permission: { + read: "allow", + edit: "deny", + }, + }, + }, + }) + + s.updateConfig({ agent: { reviewer: { permission: { bash: "ask" } } } }) + + expect(s.config.agent?.reviewer?.permission).toEqual({ + read: "allow", + edit: "deny", + bash: "ask", + }) + expect(s.draft.agent?.reviewer?.permission).toEqual({ bash: "ask" }) + }) + + it("keeps nested permission delete sentinels in the draft", () => { + const s = new ConfigState() + s.handleConfigLoaded({ + agent: { + docs: { + permission: { + edit: { "*": "deny", "**/*.md": "allow" }, + }, + }, + }, + }) + + s.updateConfig({ agent: { docs: { permission: { edit: { "**/*.md": null } } } } }) + + expect(s.config.agent?.docs?.permission).toEqual({ edit: { "*": "deny" } }) + expect(s.draft.agent?.docs?.permission).toEqual({ edit: { "**/*.md": null } }) + expect(JSON.parse(JSON.stringify(s.draft))).toEqual({ + agent: { docs: { permission: { edit: { "**/*.md": null } } } }, + }) + }) + + it("keeps tool-level permission delete sentinels in the draft", () => { + const s = new ConfigState() + s.handleConfigLoaded({ + agent: { + reviewer: { + permission: { + read: "allow", + bash: "deny", + }, + }, + }, + }) + + s.updateConfig({ agent: { reviewer: { permission: { bash: null } } } }) + + expect(s.config.agent?.reviewer?.permission).toEqual({ read: "allow" }) + expect(s.draft.agent?.reviewer?.permission).toEqual({ bash: null }) + expect(JSON.parse(JSON.stringify(s.draft))).toEqual({ + agent: { reviewer: { permission: { bash: null } } }, + }) + }) + }) }) diff --git a/packages/kilo-vscode/tests/unit/navigate.test.ts b/packages/kilo-vscode/tests/unit/navigate.test.ts index 61d7110eb60..819a6c15f89 100644 --- a/packages/kilo-vscode/tests/unit/navigate.test.ts +++ b/packages/kilo-vscode/tests/unit/navigate.test.ts @@ -4,6 +4,7 @@ import { validateLocalSession, adjacentHint, restoreLocalSessions, + reconcileLocalSessions, LOCAL, } from "../../webview-ui/agent-manager/navigate" @@ -294,3 +295,90 @@ describe("restoreLocalSessions", () => { expect(result).toBeUndefined() }) }) + +describe("reconcileLocalSessions", () => { + const isPending = (id: string) => id.startsWith("pending-") + + it("keeps restored local sessions through a partial restart refresh", () => { + const managed = [ + { id: "local-1", worktreeId: null }, + { id: "worktree-1", worktreeId: "wt-1" }, + ] + const restored = restoreLocalSessions(managed, [], undefined, isPending, (items) => items)?.filter(Boolean) ?? [] + + const result = reconcileLocalSessions(restored, ["worktree-1"], managed, isPending) + + expect(restored).toEqual(["local-1"]) + expect(result).toBeUndefined() + }) + + it("preserves restored local sessions before sessionsLoaded includes them", () => { + const result = reconcileLocalSessions( + ["s1", "s2"], + [], + [ + { id: "s1", worktreeId: null }, + { id: "s2", worktreeId: null }, + ], + isPending, + ) + + expect(result).toBeUndefined() + }) + + it("does not forget persisted local sessions when only worktree sessions loaded", () => { + const result = reconcileLocalSessions( + ["local-1"], + ["worktree-1"], + [ + { id: "local-1", worktreeId: null }, + { id: "worktree-1", worktreeId: "wt-1" }, + ], + isPending, + ) + + expect(result).toBeUndefined() + }) + + it("waits for managed state before removing sessions restored from webview state", () => { + const beforeState = reconcileLocalSessions(["local-1"], ["worktree-1"], [], isPending) + const afterState = reconcileLocalSessions( + ["local-1"], + ["worktree-1"], + [ + { id: "local-1", worktreeId: null }, + { id: "worktree-1", worktreeId: "wt-1" }, + ], + isPending, + ) + + expect(beforeState).toEqual({ ids: [], forget: ["local-1"] }) + expect(afterState).toBeUndefined() + }) + + it("forgets stale local sessions missing from loaded and managed state", () => { + const result = reconcileLocalSessions(["s1", "gone"], ["s1"], [{ id: "s1", worktreeId: null }], isPending) + + expect(result).toEqual({ ids: ["s1"], forget: ["gone"] }) + }) + + it("evicts worktree sessions that raced into local state without forgetting them", () => { + const result = reconcileLocalSessions( + ["local-1", "worktree-1"], + ["local-1", "worktree-1"], + [ + { id: "local-1", worktreeId: null }, + { id: "worktree-1", worktreeId: "wt-1" }, + ], + isPending, + ) + + expect(result).toEqual({ ids: ["local-1"], forget: [] }) + }) + + it("keeps pending local tabs during reconciliation", () => { + const result = reconcileLocalSessions(["pending-1", "gone"], [], [], isPending) + + expect(result).toEqual({ ids: ["pending-1"], forget: ["gone"] }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/permission-editor.test.ts b/packages/kilo-vscode/tests/unit/permission-editor.test.ts new file mode 100644 index 00000000000..f8df3576677 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/permission-editor.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "bun:test" +import { effectiveRuleLevel } from "../../webview-ui/src/components/settings/permission-utils" +import type { PermissionRuleItem } from "../../webview-ui/src/types/messages" + +describe("effectiveRuleLevel", () => { + it("uses the last matching wildcard rule from resolved agent rules", () => { + const rules: PermissionRuleItem[] = [ + { permission: "*", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "*", action: "ask" }, + { permission: "*", pattern: "*", action: "deny" }, + ] + + expect(effectiveRuleLevel(rules, "bash")).toBe("deny") + expect(effectiveRuleLevel(rules, "external_directory")).toBe("deny") + }) + + it("uses specific tool rules after wildcard rules", () => { + const rules: PermissionRuleItem[] = [ + { permission: "*", pattern: "*", action: "deny" }, + { permission: "read", pattern: "*", action: "allow" }, + { permission: "grep", pattern: "*", action: "allow" }, + ] + + expect(effectiveRuleLevel(rules, "read")).toBe("allow") + expect(effectiveRuleLevel(rules, "grep")).toBe("allow") + expect(effectiveRuleLevel(rules, "edit")).toBe("deny") + }) + + it("falls back to ask when no resolved wildcard rule is available", () => { + expect(effectiveRuleLevel(undefined, "bash")).toBe("ask") + expect(effectiveRuleLevel([], "bash")).toBe("ask") + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index af24000a882..b5d270f87e2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -75,6 +75,7 @@ import { VSCodeProvider, useVSCode } from "../src/context/vscode" import { ServerProvider } from "../src/context/server" import { ProviderProvider } from "../src/context/provider" import { ConfigProvider } from "../src/context/config" +import { DisplayProvider } from "../src/context/display" import { NotificationsProvider } from "../src/context/notifications" import { SessionProvider, useSession } from "../src/context/session" import { WorktreeModeProvider } from "../src/context/worktree-mode" @@ -84,7 +85,7 @@ import { NewWorktreeDialog } from "./NewWorktreeDialog" import { LanguageBridge, DataBridge } from "../src/App" import { useLanguage } from "../src/context/language" import { formatRelativeDate } from "../src/utils/date" -import { validateLocalSession, nextSelectionAfterDelete, adjacentHint, restoreLocalSessions, LOCAL } from "./navigate" +import { nextSelectionAfterDelete, adjacentHint, restoreLocalSessions, reconcileLocalSessions, LOCAL } from "./navigate" import { reorderTabs, applyTabOrder, firstOrderedTitle } from "./tab-order" import { createTabOrderSync } from "./tab-order-sync" import { ConstrainDragYAxis } from "./sortable-tab" @@ -720,16 +721,18 @@ const AgentManagerContent: Component = () => { // Invalidate local session IDs if they no longer exist (preserve pending tabs) createEffect(() => { + if (!worktreesLoaded()) return const all = session.sessions() if (all.length === 0) return // sessions not loaded yet - const ids = all.map((s) => s.id) - const prev = localSessionIDs() - const valid = prev.filter((lid) => isPending(lid) || validateLocalSession(lid, ids)) - if (valid.length !== prev.length) { - const removed = prev.filter((lid) => !isPending(lid) && !valid.includes(lid)) - for (const id of removed) vscode.postMessage({ type: "agentManager.forgetSession", sessionId: id }) - setLocalSessionIDs(valid) - } + const next = reconcileLocalSessions( + localSessionIDs(), + all.map((s) => s.id), + managedSessions(), + isPending, + ) + if (!next) return + for (const id of next.forget) vscode.postMessage({ type: "agentManager.forgetSession", sessionId: id }) + setLocalSessionIDs(next.ids) }) // Drop in-memory review state for worktrees that no longer exist. createEffect(() => { @@ -3164,17 +3167,19 @@ export const AgentManagerApp: Component = () => { - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts b/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts index b9098a99c4a..d61a8265efb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts @@ -129,6 +129,35 @@ export function restoreLocalSessions( return changed ? merged : undefined } +export function reconcileLocalSessions( + current: string[], + loaded: string[], + managed: { id: string; worktreeId: string | null }[], + isPending: (id: string) => boolean, +): { ids: string[]; forget: string[] } | undefined { + const seen = new Set(loaded) + const local = new Set(managed.filter((s) => !s.worktreeId).map((s) => s.id)) + const worktree = new Set(managed.filter((s) => s.worktreeId).map((s) => s.id)) + const ids: string[] = [] + const forget: string[] = [] + + for (const id of current) { + if (isPending(id)) { + ids.push(id) + continue + } + if (worktree.has(id)) continue + if (seen.has(id) || local.has(id)) { + ids.push(id) + continue + } + forget.push(id) + } + + if (ids.length === current.length && forget.length === 0) return undefined + return { ids, forget } +} + /** * After removing a worktree, pick the nearest remaining sidebar neighbor. * Order: the worktree just below → the one above → LOCAL. diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index c774defdac5..8665a763fa0 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -16,6 +16,7 @@ import { VSCodeProvider, useVSCode } from "./context/vscode" import { ServerProvider, useServer } from "./context/server" import { ProviderProvider, useProvider } from "./context/provider" import { ConfigProvider } from "./context/config" +import { DisplayProvider } from "./context/display" import { IndexingProvider } from "./context/indexing" import { SessionProvider, useSession } from "./context/session" import { LanguageProvider } from "./context/language" @@ -349,15 +350,17 @@ const App: Component = () => { - - - - - - - - - + + + + + + + + + + + diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 0b4a5347d82..01d03b0a5b3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -18,6 +18,8 @@ import type { } from "@kilocode/sdk/v2" import { useData } from "@kilocode/kilo-ui/context/data" import { useSession } from "../../context/session" +import { useDisplay } from "../../context/display" +import { useConfig } from "../../context/config" import { QuestionDock } from "./QuestionDock" import { SuggestBar } from "./SuggestBar" @@ -83,9 +85,37 @@ function TodoToolCard(props: { part: ToolPart }) { ) } +function BashToolCard(props: { part: ToolPart; defaultOpen: boolean }) { + const render = ToolRegistry.render(props.part.tool) + const state = props.part.state as any + return ( + + {(card) => ( + >} + input={state?.input ?? {}} + metadata={state?.metadata ?? {}} + partMetadata={props.part.metadata ?? {}} + tool={props.part.tool} + partID={props.part.id} + callID={props.part.callID} + output={state?.output} + status={state?.status} + defaultOpen={props.defaultOpen} + animate + reveal={state?.status === "pending" || state?.status === "running"} + /> + )} + + ) +} + export const AssistantMessage: Component = (props) => { const data = useData() const session = useSession() + const display = useDisplay() + const { config } = useConfig() + const open = createMemo(() => config().terminal_command_display !== "collapsed") const parts = createMemo(() => { const stored = data.store.part?.[props.message.id] @@ -107,9 +137,18 @@ export const AssistantMessage: Component = (props) => { // Active suggestion tool parts render the interactive SuggestBar inline const activeSuggestion = createMemo(() => matchToolRequest(part, "suggest", session.suggestions())) + const bash = createMemo(() => { + if (part.type !== "tool") return + const tool = part as unknown as ToolPart + if (tool.tool !== "bash") return + if (tool.state?.status === "error") return + return part + }) return ( - +
= (props) => { when={activeSuggestion()} fallback={ } - /> + > + + } > - + {(tool) => } } > diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 0b656f5c02f..46daa3d76f3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -190,116 +190,120 @@ export const ChatView: Component = (props) => { vscode.postMessage({ type: "agentManager.requestRepoInfo" }) }) + const canStartSession = (hasChat: boolean) => hasChat + + const canStartWorktree = () => isSidebar() && server.gitInstalled() + + const canMoveToWorktree = (hasChat: boolean) => hasChat && canContinueInWorktree() && server.gitInstalled() + + const hasActions = (hasChat: boolean) => canStartSession(hasChat) || canStartWorktree() || canMoveToWorktree(hasChat) + const renderActions = (hasChat: boolean) => ( -
-
- - - - -
- + +
+
+ + - worktreeRef?.getBoundingClientRect()}> - - + +
+ + - - - - - - - - {language.t("agentManager.dialog.configureWorktree")} - - - - - -
-
- - <> - - - - - - - - + worktreeRef?.getBoundingClientRect()}> + + + + + + + + + + + + + {language.t("agentManager.dialog.configureWorktree")} + + + + + +
+ + + <> + + + + + + + + +
-
+ ) return ( @@ -332,7 +336,7 @@ export const ChatView: Component = (props) => { /> )} - + {renderActions(hasMessages())} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx index 55ddd9d102a..7c262d26e3d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx @@ -22,7 +22,7 @@ export function registerVscodeToolOverrides() { ToolRegistry.register({ name, - render: (props) => , + render: (props) => , }) registered.add(name) } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx index 5bc297fd9bd..c63cb533504 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx @@ -1,153 +1,8 @@ -import { Component, For, Show, createEffect, createMemo, createSignal } from "solid-js" -import { Select } from "@kilocode/kilo-ui/select" -import { IconButton } from "@kilocode/kilo-ui/icon-button" +import { Component, createMemo } from "solid-js" + import { useConfig } from "../../context/config" import { useLanguage } from "../../context/language" -import type { PermissionLevel, PermissionRule } from "../../types/messages" - -interface LevelOption { - value: PermissionLevel - labelKey: string -} - -const LEVEL_OPTIONS: LevelOption[] = [ - { value: "allow", labelKey: "settings.autoApprove.level.allow" }, - { value: "ask", labelKey: "settings.autoApprove.level.ask" }, - { value: "deny", labelKey: "settings.autoApprove.level.deny" }, -] - -interface GranularConfig { - wildcardKey: string - addKey: string - placeholderKey: string -} - -interface ToolDef { - id: string - descriptionKey: string - granular?: GranularConfig -} - -interface GranularToolDef extends ToolDef { - granular: GranularConfig -} - -/** Grouped tool: maps a single UI row to multiple config keys */ -interface GroupedToolDef { - ids: string[] - label: string - descriptionKey: string -} - -const GRANULAR_TOOLS: GranularToolDef[] = [ - { - id: "external_directory", - descriptionKey: "settings.autoApprove.tool.external_directory", - granular: { - wildcardKey: "settings.autoApprove.wildcardLabel.paths", - addKey: "settings.autoApprove.addPath", - placeholderKey: "settings.autoApprove.placeholder.path", - }, - }, - { - id: "bash", - descriptionKey: "settings.autoApprove.tool.bash", - granular: { - wildcardKey: "settings.autoApprove.wildcardLabel.commands", - addKey: "settings.autoApprove.addCommand", - placeholderKey: "settings.autoApprove.placeholder.command", - }, - }, - { - id: "read", - descriptionKey: "settings.autoApprove.tool.read", - granular: { - wildcardKey: "settings.autoApprove.wildcardLabel.paths", - addKey: "settings.autoApprove.addPath", - placeholderKey: "settings.autoApprove.placeholder.path", - }, - }, - { - id: "edit", - descriptionKey: "settings.autoApprove.tool.edit", - granular: { - wildcardKey: "settings.autoApprove.wildcardLabel.paths", - addKey: "settings.autoApprove.addPath", - placeholderKey: "settings.autoApprove.placeholder.path", - }, - }, -] - -const SIMPLE_TOOLS: ToolDef[] = [ - { id: "glob", descriptionKey: "settings.autoApprove.tool.glob" }, - { id: "grep", descriptionKey: "settings.autoApprove.tool.grep" }, - { id: "list", descriptionKey: "settings.autoApprove.tool.list" }, - { id: "task", descriptionKey: "settings.autoApprove.tool.task" }, - { id: "skill", descriptionKey: "settings.autoApprove.tool.skill" }, - { id: "lsp", descriptionKey: "settings.autoApprove.tool.lsp" }, -] - -const GROUPED_TOOLS: GroupedToolDef[] = [ - { - ids: ["todoread", "todowrite"], - label: "todoread / todowrite", - descriptionKey: "settings.autoApprove.tool.todoreadwrite", - }, - { - ids: ["websearch", "codesearch"], - label: "websearch / codesearch", - descriptionKey: "settings.autoApprove.tool.websearchcodesearch", - }, -] - -const TRAILING_TOOLS: ToolDef[] = [ - { id: "webfetch", descriptionKey: "settings.autoApprove.tool.webfetch" }, - { id: "doom_loop", descriptionKey: "settings.autoApprove.tool.doom_loop" }, -] - -/** - * Backend default permission levels — mirrors the base defaults defined in - * packages/opencode/src/agent/agent.ts (lines 61-78). The global default - * is "*": "allow"; these are the per-tool overrides. If the backend defaults - * change, this map must be updated to match. - */ -const TOOL_DEFAULTS: Partial> = { - doom_loop: "ask", - external_directory: "ask", -} - -const RESTRICTION_ORDER: Record = { allow: 0, ask: 1, deny: 2 } - -/** For grouped tools, return the most restrictive level across all IDs. */ -function mostRestrictive(levels: PermissionLevel[]): PermissionLevel { - return levels.reduce( - (best, l) => (RESTRICTION_ORDER[l] > RESTRICTION_ORDER[best] ? l : best), - levels[0] ?? "allow", - ) -} - -function wildcardAction(rule: PermissionRule | undefined, fallback: PermissionLevel): PermissionLevel { - if (!rule) return fallback - if (typeof rule === "string") return rule - return rule["*"] ?? fallback -} - -function exceptions(rule: PermissionRule | undefined): Array<{ pattern: string; action: PermissionLevel }> { - if (!rule || typeof rule === "string") return [] - return Object.entries(rule) - .filter(([key, action]) => key !== "*" && action !== null) - .map(([pattern, action]) => ({ pattern, action: action as PermissionLevel })) -} - -function toolTitle(id: string): string { - return id - .split("_") - .map((word) => word[0].toUpperCase() + word.slice(1)) - .join(" ") - .split(" / ") - .map((part) => part[0].toUpperCase() + part.slice(1)) - .join(" / ") -} +import PermissionEditor from "./PermissionEditor" const AutoApproveTab: Component = () => { const { config, updateConfig } = useConfig() @@ -155,366 +10,11 @@ const AutoApproveTab: Component = () => { const permissions = createMemo(() => config().permission ?? {}) - const globalFallback = createMemo((): PermissionLevel => { - const star = permissions()["*"] - if (typeof star === "string") return star - return "allow" // backend default: "*": "allow" (agent.ts) - }) - - const defaultFor = (tool: string): PermissionLevel => TOOL_DEFAULTS[tool] ?? globalFallback() - - const levelFor = (tool: string): PermissionLevel => wildcardAction(permissions()[tool], defaultFor(tool)) - - const ruleFor = (tool: string): PermissionRule | undefined => permissions()[tool] - - const setSimple = (tool: string, level: PermissionLevel) => { - updateConfig({ permission: { [tool]: level } }) - } - - const setGrouped = (ids: string[], level: PermissionLevel) => { - const patch: Record = {} - for (const id of ids) patch[id] = level - updateConfig({ permission: patch }) - } - - const setWildcard = (tool: string, level: PermissionLevel) => { - const current = ruleFor(tool) - const excs = exceptions(current) - if (excs.length === 0) { - updateConfig({ permission: { [tool]: level } }) - return - } - const obj: Record = { "*": level } - for (const exc of excs) obj[exc.pattern] = exc.action - updateConfig({ permission: { [tool]: obj } }) - } - - const setException = (tool: string, pattern: string, level: PermissionLevel) => { - const current = ruleFor(tool) - const base: Record = - typeof current === "string" ? { "*": current } : { ...(current ?? {}) } - base[pattern] = level - updateConfig({ permission: { [tool]: base } }) - } - - const addException = (tool: string, pattern: string) => { - const current = ruleFor(tool) - const base: Record = - typeof current === "string" ? { "*": current } : { ...(current ?? {}) } - base[pattern] = "allow" - updateConfig({ permission: { [tool]: base } }) - } - - const removeException = (tool: string, pattern: string) => { - const current = ruleFor(tool) - if (!current || typeof current === "string") return - // Send a single patch with null for the deleted key. - // null is a delete sentinel: patchJsonc removes the key from the JSONC file, - // stripNulls removes it from the optimistic UI. - updateConfig({ permission: { [tool]: { [pattern]: null } } }) - } - return ( -
-
- {language.t("settings.autoApprove.description")} -
- - - {(tool) => ( - setWildcard(tool.id, level)} - onExceptionChange={(pattern, level) => setException(tool.id, pattern, level)} - onExceptionAdd={(pattern) => addException(tool.id, pattern)} - onExceptionRemove={(pattern) => removeException(tool.id, pattern)} - /> - )} - - - - {(tool) => ( - setSimple(tool.id, level)} - /> - )} - - - - {(group) => ( - setGrouped(group.ids, level)} - /> - )} - - - - {(tool) => ( - setSimple(tool.id, level)} - /> - )} - -
- ) -} - -const SimpleToolRow: Component<{ - id: string - descriptionKey: string - level: PermissionLevel - onChange: (level: PermissionLevel) => void -}> = (props) => { - const language = useLanguage() - return ( -
-
-
- {toolTitle(props.id)} -
-
- {language.t(props.descriptionKey)} -
-
- -
- ) -} - -const GranularToolRow: Component<{ - tool: GranularToolDef - rule: PermissionRule | undefined - fallback: PermissionLevel - onWildcardChange: (level: PermissionLevel) => void - onExceptionChange: (pattern: string, level: PermissionLevel) => void - onExceptionAdd: (pattern: string) => void - onExceptionRemove: (pattern: string) => void -}> = (props) => { - const language = useLanguage() - const [adding, setAdding] = createSignal(false) - const [input, setInput] = createSignal("") - let inputRef: HTMLInputElement | undefined - - createEffect(() => { - if (adding()) inputRef?.focus() - }) - - const excs = createMemo(() => exceptions(props.rule)) - const level = createMemo(() => wildcardAction(props.rule, props.fallback)) - - const submit = () => { - const val = input().trim() - if (val) { - props.onExceptionAdd(val) - setInput("") - } - setAdding(false) - } - - const cancel = () => { - setInput("") - setAdding(false) - } - - return ( -
- {/* Tool header with name and description */} -
-
-
- {toolTitle(props.tool.id)} -
-
- {language.t(props.tool.descriptionKey)} -
-
-
- - {/* Wildcard row */} -
-
-
- {language.t(props.tool.granular.wildcardKey)} -
-
- -
- - {/* Exceptions */} - 0}> -
-
- {language.t("settings.autoApprove.exceptions")} -
- - {(exc) => ( -
-
- {exc.pattern} -
-
- props.onExceptionChange(exc.pattern, level)} /> - props.onExceptionRemove(exc.pattern)} - /> -
-
- )} -
-
-
- - {/* Add button / inline input */} - setAdding(true)} - > - + - {language.t(props.tool.granular.addKey)} - - } - > -
- (inputRef = el)} - type="text" - value={input()} - onInput={(e) => setInput(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === "Enter") submit() - if (e.key === "Escape") cancel() - }} - onBlur={() => { - if (!input().trim()) cancel() - }} - placeholder={language.t(props.tool.granular.placeholderKey)} - style={{ - flex: 1, - "min-width": 0, - background: "var(--surface-strong-base, #252526)", - border: "1px solid var(--border-base, #434443)", - "border-radius": "2px", - color: "var(--text-base, #ccc)", - "font-size": "13px", - "font-family": "var(--vscode-editor-font-family, monospace)", - padding: "4px 8px", - outline: "none", - }} - /> - -
-
-
- ) -} - -const ActionSelect: Component<{ - level: PermissionLevel - onChange: (level: PermissionLevel) => void -}> = (props) => { - const language = useLanguage() - return ( - { triggerVariant="settings" /> + + + { + display.setReasoningAutoCollapse(checked) + }} + hideLabel + > + {language.t("settings.display.reasoningAutoCollapse.title")} + + + + + (ref = el)} + type="text" + value={input()} + onInput={(e) => setInput(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === "Enter") submit() + if (e.key === "Escape") cancel() + }} + onBlur={() => { + if (!input().trim()) cancel() + }} + placeholder={language.t(props.tool.granular.placeholderKey)} + style={{ + flex: 1, + "min-width": 0, + background: "var(--surface-strong-base, #252526)", + border: "1px solid var(--border-base, #434443)", + "border-radius": "2px", + color: "var(--text-base, #ccc)", + "font-size": "13px", + "font-family": "var(--vscode-editor-font-family, monospace)", + padding: "4px 8px", + outline: "none", + }} + /> + +
+ +
+ ) +} + +const ActionSelect: Component<{ + level: PermissionLevel + inherited?: boolean + onChange: (level: PermissionLevel) => void + onInherit?: () => void +}> = (props) => { + const language = useLanguage() + const opts = createMemo(() => (props.onInherit ? [INHERIT_OPTION, ...LEVEL_OPTIONS] : LEVEL_OPTIONS)) + return ( +