Merge remote-tracking branch 'origin/main' into obtainable-olive

This commit is contained in:
marius-kilocode
2026-07-03 16:32:25 +02:00
1063 changed files with 52745 additions and 12766 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ This directory contains changeset files used to track changes for the next relea
## Adding a changeset
When making a user-facing change, run:
When making a user-facing change, prefer one concise changeset per PR, grouping related changes when possible. Run:
```sh
bunx changeset add
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Support NVIDIA Parakeet TDT 0.6B v3 for speech-to-text input.
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Add a sandbox toggle to the Agent Manager New Worktree modal so each worktree session can start sandboxed
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Animate tool-call and reasoning details when expanding and collapsing.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Keep turns responsive when snapshot infrastructure stalls and prevent transient snapshot progress from appearing in forked sessions.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Enable autocomplete in Jupyter notebooks.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Run Windows PowerShell tool commands without `-EncodedCommand` to reduce antivirus false positives.
-6
View File
@@ -1,6 +0,0 @@
---
"kilo-code": patch
"@kilocode/kilo-gateway": patch
---
Use the matching FIM model for chat autocomplete when Next Edit is selected.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Support adaptive reasoning presets for Claude Fable and Sonnet 5 models.
@@ -0,0 +1,7 @@
---
"kilo-code": patch
---
Free webview memory for deleted VS Code sessions by clearing unsent prompt text, review comments, and pending image attachments that were retained in the per-session draft cache after `sessionDeleted`.
Also restores an in-flight failed draft into the live prompt after a session is deleted mid-send (whether user-initiated or via external CLI/TUI/cascade delete), while never rehydrating it into a prompt the user explicitly cleared.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Explain Marketplace item types, installation scopes, destination files, and MCP security before installation.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Create the default `.kilo/plans` directory automatically when Plan mode starts.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Show experimental sandbox controls by default for non-Windows users.
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Filter marketplace items to those relevant to the current workspace's files and installed VS Code extensions.
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Keep the chat in sync with the selected Agent Manager session when the backend connection is briefly unavailable, so switching sessions no longer updates only the side diff while the conversation stays on the previous session.
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Fix AWS Bedrock requests failing in the VS Code extension when Bedrock is configured through SSO or an AWS credential chain.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Fix slow message loading when opening or switching sessions. The per-model token usage breakdown scanned the entire message history on every session open, which blocked the transcript from rendering for several seconds on large histories.
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Close read-only bash escapes where allowed commands could still run arbitrary programs via flags (`sort --compress-program`, `rg --pre`, `ag --pager`, `man -P`/`-H`).
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Use `/review` as the single local review command, defaulting to staged, unstaged, and untracked changes while supporting guided uncommitted reviews, branch/base reviews, commits, and pull requests. Show deprecation notices for `/local-review` and `/local-review-uncommitted` that point to the matching `/review` modes.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Align subagent and MCP output surfaces with other tool results and prevent completed subagent results from opening with a clipped row.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---
Enforce the sandbox network restriction for agent commands on Linux, including TCP, UDP, IPv4, IPv6, and descendant processes.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Render JetBrains todo checklists with consistent text weight and higher-contrast checkboxes.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Highlight rendered prompt file mentions and open them on click.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show resolved JetBrains config file paths, float connection status above the prompt, and offer retry, restart, and reinstall recovery actions from connection errors. JetBrains now opens the same global config directory used by the CLI; macOS and Windows users who previously created global config from JetBrains may need to move files from the old platform-specific location to `~/.config/kilo`.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show JetBrains file mention suggestions immediately for empty `@` mentions and keep the completion popup stable while typing quickly.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Hide raw file contents from mentioned files in JetBrains chat messages.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Fix @git-changes mentions causing JetBrains chat sessions to fail.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Match JetBrains inline code and file-reference link styling with VS Code, and render quotes with muted theme-aware styling.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show file type icons and keep predefined mentions first in JetBrains mention completions.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": minor
---
Show expandable model details in the JetBrains model picker.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show JetBrains missing-file warnings without animation and always show shell command header popups for collapsed shell runs.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": minor
---
Add `/` slash commands and `@` file/git-changes mentions to the JetBrains chat prompt with native completion.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show a concise single-line hint in the JetBrains prompt placeholder.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Fix prompt undo/redo, clean mid-token mention completion, and show unresolved file mentions in the JetBrains chat prompt.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Attach hand-typed prompt file mentions when sending immediately.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve JetBrains session error logs so nested CLI error messages are visible.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Open JetBrains session file links in the active workspace and hide sibling worktree matches.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Restore prompt focus after returning from session history in JetBrains.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Clear stale session link hover styling when the transcript is scrolled.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support VS Code slash-command aliases in the JetBrains prompt.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep JetBrains prompt mention suggestions stable while typing fuzzy file matches.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep JetBrains tool headers to a single clipped line.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Preserve new-chat prompts and attachments when toggling sandbox before sending the first message.
+7
View File
@@ -0,0 +1,7 @@
---
"@kilocode/kilo-memory": minor
---
Add the `@kilocode/kilo-memory` foundation package: project memory storage, indexing, recall,
consolidation, and the Effect runtime layer (service, capture orchestration, and runtime ports).
Host wiring (CLI tools, prompts, HTTP API) lands in follow-up CLI/extension PRs.
-7
View File
@@ -1,7 +0,0 @@
---
"@kilocode/cli": patch
"@kilocode/kilo-gateway": patch
"@kilocode/sdk": patch
---
Expose Kilo Pass state on the Kilo profile API contract.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---
Block outbound network access from agent commands and in-process HTTP tools with the optional macOS sandbox, with a Sandboxing setting to allow network access when needed.
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Notify when a marketplace item matches your workspace, with a one-click install button and a "Don't show again" option per suggestion.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Browse skills, agents, and MCP servers together and filter them by category.
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---
Automatically give Agent Manager branches task-focused names once the conversation establishes a clear workstream, with optional prefixes and a setting to disable automatic naming.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Support safely reading, editing, and executing Jupyter notebook cells directly from Kilo in VS Code, including absolute workspace paths and content-based edit conflict detection.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Show sandbox state changes as concise VS Code notifications.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": minor
---
Persist the `/sandbox` toggle across new CLI sessions per project directory, mirroring the VS Code extension's sandbox button. New sessions now inherit the last toggled state instead of resetting to the config default each time.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Preserve unchanged codebase indexes when extension or VS Code updates interrupt an incremental scan.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Preserve the model picker preview panel expand/collapse state across reloads and restarts.
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Report the plan file that was actually saved in Plan mode: point the "Plan is ready" link, the follow-up prompt, and the new-session handoff at the real file instead of a wrongly generated name, and fail plan_exit with a clear error when no plan was written.
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Show an interactive Implement / Keep refining panel when Plan mode is ready instead of asking users to type a numbered choice.
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Preserve model output capacity when requests contain encoded images. The output token cap now uses the provider-reported context size from the previous turn, so image and vision input is measured by the provider instead of by encoded payload size.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Make "Show more providers" a prominent entry in the Popular providers list and collapse Disabled Providers by default in the Providers settings.
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Keep attention sounds silent for permission requests handled by auto-approve.
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Remove the balance chip from the session header.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Support resetting read notifications from the VS Code extension settings.
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Retain the sandbox toggle state when forking a session or moving it to a worktree, instead of resetting it to the workspace default.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---
Confine agent shell and file-tool writes to project and Kilo state directories with the optional macOS and Linux sandboxes.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Keep sandboxing disabled by default unless the experimental sandbox setting or an explicit session toggle enables it.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Prevent sandboxed file tools from escaping project write roots through concurrent symlink replacement on macOS.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Confine sandboxed worktree sessions to their active worktree instead of allowing writes to sibling or primary checkouts.
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Remember the model picker expand/collapse choice across the sidebar and the Agent Manager
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Fix shell tool occasionally returning "(no output)" for fast-exiting commands
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Show the usable local IPv6 URL when the server binds to the IPv6 wildcard address.
-6
View File
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Show the concrete model reported for routed Kilo auto-model steps in CLI and VS Code session timelines.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Show filesystem and network restriction states in the sandbox tooltip.
-4
View File
@@ -1,4 +0,0 @@
---
"kilo-code": patch
---
Sort slash-command dropdown results by relevance. Exact matches now appear first, followed by prefix matches, then substring matches.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep JetBrains sessions scoped to the correct worktree when multiple IntelliJ windows are open.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Add `/cost-alert` to get notified when a session's cost crosses a threshold you set.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Warn before continuing a session whose spend is above the configured max cost. Opt-in and disabled by default — set a whole-dollar Session Cost Alert under Auto-Approve settings to enable it.
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"@kilocode/kilo-jetbrains": patch
---
Improve JetBrains agent, MCP, provider, and model settings so changes are staged until Apply, persist through the CLI, reload accurately, and hide unsupported removal actions.
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Fix non-default agents (Ask, Plan, and custom or organization agents) failing with a "Bad Request: Unsupported parameter(s)" error on some models and providers.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Let users hide the codebase indexing button while indexing is off.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Toggle the current session sandbox with `/sandbox` in the sidebar and Agent Manager.
-7
View File
@@ -1,7 +0,0 @@
---
"@kilocode/cli": minor
"@kilocode/sdk": minor
"kilo-code": patch
---
Add session-local macOS sandbox controls, show the effective active state, and confirm toggles in the CLI and VS Code extension.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Use model family metadata when selecting the apply_patch tool for GPT models.
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Stop shipping the local-only `.cli-version` build marker in packaged VSIX installs, which previously made production installs detect as local builds and inject a dev-only bwrap fallback.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Show account balance in the VS Code header account switcher and surface Kilo Pass details on the profile page.
+13 -1
View File
@@ -10,15 +10,27 @@ packages/kilo-ui/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
packages/kilo-docs/public/img/screenshot-tests/**/*.png filter=lfs diff=lfs merge=lfs -text
# Hide non-English localization files by default in GitHub PR diffs.
# Hide most localization files by default in GitHub PR diffs. Show English as the source
# language and German so reviewers can catch untranslated copy-pasted English strings.
**/i18n/*.ts linguist-generated=true
**/i18n/**/*.ts linguist-generated=true
**/i18n/en.ts linguist-generated=false
**/i18n/en*.ts linguist-generated=false
**/i18n/**/en.ts linguist-generated=false
**/i18n/**/en*.ts linguist-generated=false
**/i18n/de.ts linguist-generated=false
**/i18n/de*.ts linguist-generated=false
**/i18n/**/de.ts linguist-generated=false
**/i18n/**/de*.ts linguist-generated=false
**/i18n/package-nls-en.ts linguist-generated=false
**/i18n/package-nls-de.ts linguist-generated=false
**/i18n/index.ts linguist-generated=false
**/i18n/**/index.ts linguist-generated=false
**/i18n/parity.test.ts linguist-generated=false
**/i18n/**/parity.test.ts linguist-generated=false
packages/kilo-i18n/src/*.ts linguist-generated=true
packages/kilo-i18n/src/en.ts linguist-generated=false
packages/kilo-i18n/src/de.ts linguist-generated=false
# Auto-generated CLI reference docs
packages/kilo-docs/markdoc/partials/cli-commands-table.md linguist-generated=true
+34
View File
@@ -42,12 +42,46 @@ runs:
restore-keys: |
${{ runner.os }}-bun-
# kilocode_change start
- name: Setup Node for native dependency builds
if: runner.os != 'Windows'
uses: actions/setup-node@v6
with:
node-version: "24"
package-manager-cache: false
- name: Configure node-gyp Node headers
if: runner.os != 'Windows'
id: node-gyp
run: |
node <<'NODE'
const fs = require("node:fs")
const path = require("node:path")
const exe = path.dirname(fs.realpathSync(process.execPath))
const dirs = [exe, path.dirname(exe)]
const root = dirs.find((dir) => fs.existsSync(path.join(dir, "include", "node", "node.h")))
if (!root) {
throw new Error(`Node headers not found under ${dirs.join(", ")}`)
}
fs.appendFileSync(process.env.GITHUB_OUTPUT, `nodedir=${root}\n`)
NODE
shell: bash
# kilocode_change end
- name: Install setuptools for distutils compatibility
run: python3 -m pip install setuptools || pip install setuptools || true
shell: bash
- name: Install dependencies
run: |
# kilocode_change start
if [ "$RUNNER_OS" != "Windows" ]; then
export npm_config_nodedir="${{ steps.node-gyp.outputs.nodedir }}"
fi
# kilocode_change end
# Workaround for patched peer variants
# e.g. ./patches/ for standard-openapi
# https://github.com/oven-sh/bun/issues/28147
+29 -7
View File
@@ -7,9 +7,19 @@ on:
- closed
branches:
- main
workflow_dispatch:
inputs:
pr:
description: Merged JetBrains release PR number to publish
required: true
type: string
merge_commit:
description: Merge commit SHA from the reviewed release PR
required: true
type: string
concurrency:
group: publish-jetbrains-pr-${{ github.event.pull_request.number }}
group: publish-jetbrains-pr-${{ github.event.pull_request.number || inputs.pr }}
cancel-in-progress: false
permissions:
@@ -23,17 +33,23 @@ jobs:
publish:
if: >-
github.repository == 'Kilo-Org/kilocode' &&
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') &&
contains(github.event.pull_request.labels.*.name, 'jetbrains-release') &&
github.event.pull_request.head.repo.full_name == github.repository
(
(
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') &&
contains(github.event.pull_request.labels.*.name, 'jetbrains-release') &&
github.event.pull_request.head.repo.full_name == github.repository
) ||
github.event_name == 'workflow_dispatch'
)
runs-on: blacksmith-8vcpu-ubuntu-2404
steps:
- name: Checkout merged release PR
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.merge_commit_sha }}
ref: ${{ github.event.pull_request.merge_commit_sha || inputs.merge_commit }}
- name: Setup Bun for validation
uses: ./.github/actions/setup-bun
@@ -44,7 +60,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr }}
- name: Save reviewed release metadata
run: |
@@ -86,6 +102,12 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y patchelf zip
curl --fail --location \
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
--output "$RUNNER_TEMP/zig.tar.xz"
echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status
tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
- name: Validate publishing secrets
run: |
+6 -2
View File
@@ -194,8 +194,12 @@ jobs:
test -x "$helper"
grep -q '^SPDX-License-Identifier: LGPL-2.0-or-later$' "$(dirname "$binary")/licenses/bubblewrap/NOTICE"
"$helper" --version
# The live user-namespace bootstrap depends on the runner's kernel/AppArmor policy
# (GitHub-hosted Ubuntu 24.04 sets kernel.apparmor_restrict_unprivileged_userns=1), not on
# the shipped artifact. The runtime probe degrades gracefully, so keep this check non-fatal.
"$helper" --unshare-user --disable-userns --unshare-pid --die-with-parent --new-session \
--ro-bind / / --dev /dev --proc /proc -- "$helper" --version
--ro-bind / / --dev /dev --proc /proc -- "$helper" --version \
|| echo "unprivileged user namespaces unavailable on this runner; skipping live sandbox check"
fi
root="$(mktemp -d)"
trap 'rm -rf "$root"' RETURN
@@ -233,7 +237,7 @@ jobs:
binary="/dist/$PACKAGE/bin/kilo" # kilocode_change
"$binary" --version # kilocode_change
"/dist/$PACKAGE/bin/bwrap" --version # kilocode_change
grep -q '^SPDX-License-Identifier: LGPL-2.0-or-later$' "/dist/$PACKAGE/bin/licenses/bubblewrap/NOTICE" # kilocode_change
grep -q '\''^SPDX-License-Identifier: LGPL-2.0-or-later$'\'' "/dist/$PACKAGE/bin/licenses/bubblewrap/NOTICE" # kilocode_change
root="$(mktemp -d)"
trap '\''rm -rf "$root"'\'' EXIT
unset KILO_MODELS_PATH KILO_MODELS_URL KILO_CONFIG KILO_CONFIG_DIR
+59 -13
View File
@@ -22,31 +22,73 @@ env:
jobs:
# kilocode_change start
changes:
name: detect general unit test changes
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
outputs:
settings: ${{ steps.matrix.outputs.settings }}
steps:
- name: Checkout repository
if: github.event_name != 'workflow_dispatch'
uses: actions/checkout@v6
- name: Detect general unit test changes
if: github.event_name != 'workflow_dispatch'
id: filter
uses: Kilo-Org/paths-filter@668c092af3649c4b664c54e4b704aa46782f6f7c # v3
with:
predicate-quantifier: every
filters: |
general:
- '**'
- '!.changeset/**'
- '!packages/kilo-jetbrains/**'
- '!packages/kilo-vscode/**'
- '!packages/kilo-docs/**'
- '!packages/extensions/zed/**'
- '!specs/**'
- '!perf/**'
- name: Configure unit matrix
id: matrix
env:
GENERAL: ${{ github.event_name != 'pull_request' || steps.filter.outputs.general == 'true' }}
run: |
if [ "$GENERAL" = "true" ]; then
echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":true},{"name":"macos","host":"macos-15","run":true},{"name":"windows","host":"blacksmith-4vcpu-windows-2025","run":true}]' >> "$GITHUB_OUTPUT"
exit 0
fi
echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":false}]' >> "$GITHUB_OUTPUT"
unit:
name: unit (${{ matrix.settings.name }})
needs: changes
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
- name: macos
host: macos-15 # kilocode_change
- name: windows
host: blacksmith-4vcpu-windows-2025 # kilocode_change
settings: ${{ fromJSON(needs.changes.outputs.settings) }}
runs-on: ${{ matrix.settings.host }}
timeout-minutes: 45 # kilocode_change
defaults:
run:
shell: bash
steps:
- name: Skip unchanged general unit tests
if: ${{ !matrix.settings.run }}
run: echo "Only isolated product, documentation, or metadata files changed; general unit tests are unchanged."
- name: Checkout repository
if: matrix.settings.run
uses: actions/checkout@v6 # kilocode_change
with:
token: ${{ secrets.GITHUB_TOKEN }}
# kilocode_change start
- name: Setup Node
if: matrix.settings.run
id: setup-node
continue-on-error: ${{ runner.os == 'Windows' }}
uses: actions/setup-node@v6 # kilocode_change
@@ -54,18 +96,19 @@ jobs:
node-version: "24"
- name: Retry Setup Node on Windows
if: runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
if: matrix.settings.run && runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
uses: actions/setup-node@v6
with:
node-version: "24"
# kilocode_change end
- name: Setup Bun
if: matrix.settings.run
uses: ./.github/actions/setup-bun
# kilocode_change start
- name: Setup Zig for Linux sandbox helper
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
run: |
curl --fail --location --retry 3 \
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
@@ -75,17 +118,19 @@ jobs:
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
- name: Build Linux sandbox helper
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
run: |
bun packages/opencode/script/kilocode/bubblewrap.ts --arch x64 --output "$RUNNER_TEMP/bwrap"
echo "KILO_BWRAP_PATH=$RUNNER_TEMP/bwrap" >> "$GITHUB_ENV"
# kilocode_change end
- name: Configure git identity
if: matrix.settings.run
run: |
git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com"
git config --global user.name "kilo-maintainer[bot]"
- name: Cache Turbo
if: matrix.settings.run
uses: actions/cache@v5 # kilocode_change
with:
path: node_modules/.cache/turbo
@@ -95,6 +140,7 @@ jobs:
turbo-${{ runner.os }}-
- name: Run unit tests
if: matrix.settings.run
run: bun turbo test:ci --filter='!@kilocode/kilo-jetbrains'
env:
KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
@@ -102,13 +148,13 @@ jobs:
# kilocode_change start
- name: Run HttpApi exerciser gates
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
working-directory: packages/opencode
run: bun run test:httpapi
# kilocode_change end
- name: Publish unit reports # kilocode_change
if: always()
if: always() && matrix.settings.run
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
with:
report_paths: packages/*/.artifacts/unit/junit.xml
@@ -118,7 +164,7 @@ jobs:
fail_on_failure: false
- name: Upload unit artifacts
if: always()
if: always() && matrix.settings.run
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }}
+50 -65
View File
@@ -1,3 +1,4 @@
# kilocode_change - new file
name: Visual Regression Tests
on:
@@ -10,10 +11,9 @@ jobs:
runs-on: ubuntu-latest
outputs:
matched: ${{ steps.filter.outputs.matched }}
is_fork: ${{ steps.fork-check.outputs.is_fork }}
can_autocommit: ${{ steps.autocommit-check.outputs.can_autocommit }}
steps:
- uses: actions/checkout@v6 # kilocode_change
# kilocode_change start
- uses: actions/checkout@v6
- name: Check changed files
id: filter
env:
@@ -23,7 +23,7 @@ jobs:
matched=false
while IFS= read -r file; do
case "$file" in
packages/kilo-ui/*|packages/ui/*|packages/util/*|packages/sdk/js/*|packages/kilo-vscode/webview-ui/*|packages/kilo-vscode/.storybook/*|packages/kilo-vscode/tests/visual-regression*|packages/kilo-vscode/tests/permission-dock-dropdown*|packages/kilo-vscode/tests/accessibility*|packages/kilo-docs/public/img/screenshot-tests/*|.github/workflows/visual-regression.yml)
packages/kilo-ui/*|packages/ui/*|packages/util/*|packages/sdk/js/*|packages/kilo-vscode/webview-ui/*|packages/kilo-vscode/.storybook/*|packages/kilo-vscode/tests/visual-regression*|packages/kilo-vscode/tests/permission-dock-dropdown*|packages/kilo-vscode/tests/accessibility*|packages/kilo-docs/public/img/screenshot-tests/*|.github/actions/setup-bun/*|.github/workflows/visual-regression.yml)
matched=true
break
;;
@@ -31,7 +31,6 @@ jobs:
done < <(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR}/files" --jq '.[].filename')
echo "matched=$matched" >> "$GITHUB_OUTPUT"
echo "matched=$matched"
# kilocode_change end
- name: Check if PR is from a fork
id: fork-check
run: |
@@ -40,6 +39,16 @@ jobs:
else
echo "is_fork=false" >> "$GITHUB_OUTPUT"
fi
- name: Check baseline auto-commit permissions
id: autocommit-check
env:
BOT_PAT: ${{ secrets.BOT_PAT }}
run: |
if [ "${{ steps.fork-check.outputs.is_fork }}" != "true" ] && [ -n "$BOT_PAT" ]; then
echo "can_autocommit=true" >> "$GITHUB_OUTPUT"
else
echo "can_autocommit=false" >> "$GITHUB_OUTPUT"
fi
visual-regression:
needs: check-paths
@@ -50,21 +59,22 @@ jobs:
steps:
- name: Checkout (internal)
if: needs.check-paths.outputs.is_fork != 'true'
uses: actions/checkout@v6 # kilocode_change
if: needs.check-paths.outputs.can_autocommit == 'true'
uses: actions/checkout@v6
with:
lfs: true
# use BOT_PAT only when later baseline pushes are allowed; github.token is read-only on Dependabot PRs.
token: ${{ secrets.BOT_PAT }}
ref: ${{ github.head_ref }}
- name: Checkout (fork)
if: needs.check-paths.outputs.is_fork == 'true'
uses: actions/checkout@v6 # kilocode_change
- name: Checkout (read-only)
if: needs.check-paths.outputs.can_autocommit != 'true'
uses: actions/checkout@v6
with:
lfs: true
- name: Check if HEAD is a baseline update commit
if: needs.check-paths.outputs.is_fork != 'true'
if: needs.check-paths.outputs.can_autocommit == 'true'
id: check-baseline-commit
run: |
COMMIT_MSG=$(git log -1 --format=%s)
@@ -76,22 +86,11 @@ jobs:
fi
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Cache Bun modules
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lock') }}
- name: Install dependencies
run: bun install
uses: ./.github/actions/setup-bun # kilocode_change
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v5 # kilocode_change
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('packages/kilo-ui/package.json') }}
@@ -107,7 +106,7 @@ jobs:
- name: Cache Storybook build
id: storybook-cache
uses: actions/cache@v5 # kilocode_change
uses: actions/cache@v5
with:
path: packages/kilo-ui/storybook-static
key: storybook-${{ hashFiles('packages/kilo-ui/src/**', 'packages/kilo-ui/.storybook/**', 'packages/ui/src/**', 'packages/kilo-ui/package.json') }}
@@ -144,22 +143,22 @@ jobs:
"
working-directory: packages/kilo-ui
- name: Check for baseline changes (fork PRs)
if: needs.check-paths.outputs.is_fork == 'true'
- name: Check for baseline changes (read-only PRs)
if: needs.check-paths.outputs.can_autocommit != 'true'
run: |
git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/
if git diff --cached --quiet; then
echo "No visual regression detected."
else
echo "::error::Visual regression detected. Screenshot baselines have changed."
echo "::error::Since this PR is from a fork, updated screenshots cannot be committed automatically."
echo "::error::Since this PR cannot receive baseline commits automatically, updated screenshots cannot be committed."
echo "::error::Please ask a Kilo developer for help updating the screenshots."
git diff --cached --stat
exit 1
fi
- name: Fail if baselines still changing after auto-update
if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit.outputs.is_baseline_update == 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit.outputs.is_baseline_update == 'true'
run: |
git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/
if git diff --cached --quiet; then
@@ -173,7 +172,7 @@ jobs:
fi
- name: Commit and push new baselines (if any)
if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit.outputs.is_baseline_update != 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit.outputs.is_baseline_update != 'true'
id: commit-baselines
env:
GH_TOKEN: ${{ secrets.BOT_PAT }}
@@ -192,14 +191,14 @@ jobs:
fi
- name: Fail if baselines changed
if: needs.check-paths.outputs.is_fork != 'true' && steps.commit-baselines.outputs.changed == 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines.outputs.changed == 'true'
run: |
echo "::error::Visual regression baselines changed. New baselines have been committed to the branch. Please pull and review."
exit 1
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v7 # kilocode_change
uses: actions/upload-artifact@v7
with:
name: visual-regression-results
path: packages/kilo-ui/test-results/
@@ -211,28 +210,27 @@ jobs:
name: Visual Regression (kilo-vscode webview)
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
# kilocode_change start
env:
NODE_OPTIONS: --max-old-space-size=4096
# kilocode_change end
steps:
- name: Checkout (internal)
if: needs.check-paths.outputs.is_fork != 'true'
uses: actions/checkout@v6 # kilocode_change
if: needs.check-paths.outputs.can_autocommit == 'true'
uses: actions/checkout@v6
with:
lfs: true
# use BOT_PAT only when later baseline pushes are allowed; github.token is read-only on Dependabot PRs.
token: ${{ secrets.BOT_PAT }}
ref: ${{ github.head_ref }}
- name: Checkout (fork)
if: needs.check-paths.outputs.is_fork == 'true'
uses: actions/checkout@v6 # kilocode_change
- name: Checkout (read-only)
if: needs.check-paths.outputs.can_autocommit != 'true'
uses: actions/checkout@v6
with:
lfs: true
- name: Check if HEAD is a baseline update commit
if: needs.check-paths.outputs.is_fork != 'true'
if: needs.check-paths.outputs.can_autocommit == 'true'
id: check-baseline-commit-vscode
run: |
COMMIT_MSG=$(git log -1 --format=%s)
@@ -244,22 +242,11 @@ jobs:
fi
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Cache Bun modules
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lock') }}
- name: Install dependencies
run: bun install
uses: ./.github/actions/setup-bun # kilocode_change
- name: Cache Playwright browsers
id: playwright-cache-vscode
uses: actions/cache@v5 # kilocode_change
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: playwright-vscode-${{ hashFiles('packages/kilo-vscode/package.json') }}
@@ -275,7 +262,7 @@ jobs:
- name: Cache Storybook build
id: storybook-cache-vscode
uses: actions/cache@v5 # kilocode_change
uses: actions/cache@v5
with:
path: packages/kilo-vscode/storybook-static
key: storybook-vscode-${{ hashFiles('packages/kilo-vscode/webview-ui/src/**', 'packages/kilo-vscode/.storybook/**', 'packages/kilo-ui/src/**', 'packages/ui/src/**', 'packages/kilo-vscode/package.json') }}
@@ -284,18 +271,16 @@ jobs:
if: steps.storybook-cache-vscode.outputs.cache-hit != 'true'
run: bun run build-storybook
working-directory: packages/kilo-vscode
# kilocode_change start
env:
NODE_OPTIONS: --max-old-space-size=4096
# kilocode_change end
- name: Generate baselines and enforce webview accessibility checks # kilocode_change
- name: Generate baselines and enforce webview accessibility checks
run: bun run test:visual:update
working-directory: packages/kilo-vscode
env:
CI: true
PLAYWRIGHT_WORKERS: "4"
NODE_OPTIONS: --max-old-space-size=4096 # kilocode_change
NODE_OPTIONS: --max-old-space-size=4096
- name: Remove stale baselines for deleted stories
run: |
@@ -344,22 +329,22 @@ jobs:
"
working-directory: packages/kilo-vscode
- name: Check for baseline changes (fork PRs)
if: needs.check-paths.outputs.is_fork == 'true'
- name: Check for baseline changes (read-only PRs)
if: needs.check-paths.outputs.can_autocommit != 'true'
run: |
git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/
if git diff --cached --quiet; then
echo "No visual regression detected."
else
echo "::error::Visual regression detected. Screenshot baselines have changed."
echo "::error::Since this PR is from a fork, updated screenshots cannot be committed automatically."
echo "::error::Since this PR cannot receive baseline commits automatically, updated screenshots cannot be committed."
echo "::error::Please ask a Kilo developer for help updating the screenshots."
git diff --cached --stat
exit 1
fi
- name: Fail if baselines still changing after auto-update
if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update == 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update == 'true'
run: |
git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/
if git diff --cached --quiet; then
@@ -373,7 +358,7 @@ jobs:
fi
- name: Commit and push new baselines (if any)
if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update != 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update != 'true'
id: commit-baselines-vscode
env:
GH_TOKEN: ${{ secrets.BOT_PAT }}
@@ -392,14 +377,14 @@ jobs:
fi
- name: Fail if baselines changed
if: needs.check-paths.outputs.is_fork != 'true' && steps.commit-baselines-vscode.outputs.changed == 'true'
if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines-vscode.outputs.changed == 'true'
run: |
echo "::error::Visual regression baselines changed. New baselines have been committed to the branch. Please pull and review."
exit 1
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v7 # kilocode_change
uses: actions/upload-artifact@v7
with:
name: visual-regression-vscode-results
path: packages/kilo-vscode/test-results/
+31 -1
View File
@@ -17,4 +17,34 @@ if (process.versions.bun !== expectedBunVersion) {
console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
}
'
bun typecheck
# kilocode_change start
jetbrains=0
zero=0000000000000000000000000000000000000000
while read -r local_ref local_oid remote_ref remote_oid; do
if [ "$local_oid" = "$zero" ]; then
continue
fi
base="$remote_oid"
if [ "$remote_oid" = "$zero" ]; then
base=$(git merge-base "$local_oid" "$1/main" 2>/dev/null || git hash-object -t tree /dev/null)
fi
if ! git diff --quiet "$base" "$local_oid" -- . \
':(exclude).changeset/**' \
':(exclude)packages/kilo-vscode/**' \
':(exclude)packages/kilo-docs/**'; then
jetbrains=1
fi
done
bun typecheck --filter='!@kilocode/kilo-jetbrains'
if [ "$jetbrains" -eq 1 ]; then
bun typecheck --filter='@kilocode/kilo-jetbrains'
else
echo "Skipping JetBrains typecheck (no relevant changes)"
fi
# kilocode_change end
@@ -0,0 +1,84 @@
# JetBrains Model Picker Details Plan
## Goal
Add a JetBrains model picker maximize/minimize affordance that expands the popup to roughly double width and shows model details comparable to VS Code's `ModelPreview`. Persist the last expanded/collapsed state in IntelliJ `PropertiesComponent`. First-time default is collapsed.
## Decisions
- Scope is full VS Code parity where JetBrains can receive the same model metadata: pricing, release date, descriptions, modalities/capabilities, terminal-bench, auto-routing details, context limits, badges, and provider/model identity.
- Default state is collapsed for users without a saved property.
- Expanded state is app-level UI state stored in `PropertiesComponent`, not backend/session state.
- Use Swing/IntelliJ platform components only. Do not introduce Compose, JCEF, or UI DSL.
- Keep existing compact list behavior when collapsed. In expanded mode, show a retained details panel beside the list.
## Affected Areas
- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderDto.kt`
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorkspaceState.kt`
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt`
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceDtoMapper.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/SessionModel.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/models/ModelsSettingsUi.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/model/ModelPicker*.kt`
- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle*.properties`
- Existing tests under `packages/kilo-jetbrains/frontend/src/test/...` and `backend/src/test/...`
## Implementation Steps
1. Extend model metadata DTOs.
- Add serializable nested DTOs to `ProviderDto.kt` for preview metadata matching VS Code provider types where practical: cost/cache cost, capabilities/input modalities, options/description, auto-routing model ids, terminal-bench score/cost.
- Extend `ModelDto` with nullable fields such as `inputPrice`, `outputPrice`, `contextLength`, `releaseDate`, `latest`, `cost`, `capabilities`, `options`, `autoRouting`, and `terminalBench`.
- Preserve existing fields (`limit`, `variants`, `free`, `byok`, `mayTrainOnYourPrompts`) to avoid breaking callers.
2. Parse and map the extra metadata in the JetBrains backend.
- Extend `ModelInfo` in `KiloWorkspaceState.kt` with matching nullable metadata fields.
- Update `KiloCliDataParser.parseModel` and `parseModelDto` to read the fields currently used by VS Code: `inputPrice`, `outputPrice`, `contextLength`, `releaseDate`, `latest`, `cost`, `capabilities.input`, `options.description`, `autoRouting.models`, and `terminalBench`.
- Keep permissive parsing: missing or malformed optional fields should result in `null` or empty data, not provider load failure.
- Update `KiloWorkspaceDtoMapper.model` to copy all new fields to `ModelDto`.
3. Carry metadata through the frontend session/settings model flow.
- Extend session `ModelItem` and picker `ModelPicker.Item` to hold the preview metadata.
- Update `SessionController` -> `SessionModel`, `SessionUi`, and `ModelsSettingsUi.items()` mappings so metadata survives into the picker.
- Pass `limit` through to `ModelPicker.Item`; it is already available in `ModelItem` but currently dropped in `SessionUi`.
4. Add a retained Swing details panel.
- Create a small `ModelDetailsPanel` near `session/ui/model/` or inside `ModelPicker.kt` if compact enough.
- Use `JBLabel`, `SimpleColoredComponent`, `JBHtmlPane` or safe HTML helpers, `Stack`, and `JBUI` spacing.
- Render VS Code-equivalent sections where data exists: header name/provider/star, free/BYOK/training badges, pricing, context window, release date, capabilities/modalities, terminal-bench, description, auto-routing candidates, and ids.
- Sanitize/escape model descriptions. Use `HtmlChunk`/`HtmlBuilder` or platform escaping helpers; do not concatenate unescaped HTML.
- Details panel should update from the currently previewed row in expanded mode and fall back to the active selected model when no preview row is set.
5. Rework popup layout and size behavior.
- Add an expand/minimize button to the search/header row in `ModelPicker.showPopup()`.
- Use pointer cursor on the button and localized tooltips/accessibility names: `Maximize model details` and `Minimize model details` or equivalent.
- Persist state under a key such as `kilo.model.picker.expanded` using `PropertiesComponent.getInstance().getBoolean(key, false)` and `setValue(key, value.toString())`.
- Collapsed mode keeps the current list-only width behavior.
- Expanded mode lays out list and details side-by-side with a splitter/separator and sets preferred width to approximately double the collapsed width, clamped to available screen bounds. Keep `setLocateWithinScreenBounds(true)`.
- Make `JBPopup` resizable only if the implementation can keep the retained layout stable; otherwise keep non-resizable and compute deterministic sizes.
6. Match VS Code interaction semantics where useful.
- Collapsed mode: clicking a model selects it immediately, preserving current behavior.
- Expanded mode: clicking/hovering or keyboard navigation updates the preview; Enter selects; double-click selects; Escape closes.
- Favorite star behavior remains unchanged and updates the details panel favorite state when toggled.
- When collapsing from expanded, clear transient preview state and show selected/active model on next expansion.
7. Add localization strings.
- Add base `KiloBundle.properties` entries for maximize/minimize tooltips, detail labels, pricing labels, context, capabilities, terminal-bench, description, auto-routing choices, and unavailable values.
- If existing localized files require key completeness, add English fallback values there too; otherwise rely on base bundle fallback consistent with project practice.
8. Add tests.
- Backend parser tests: ensure all new metadata fields parse from representative provider JSON and missing optional fields are tolerated.
- Mapper/serialization tests: ensure new DTO fields survive backend -> shared DTO serialization.
- `ModelPickerTest`: verify expanded property default is collapsed, toggle persists true/false, tooltip/icon state switches maximize/minimize, expanded width is greater than collapsed width, preview panel displays selected/hovered model metadata, and pointer cursor is set on the expand control.
- Renderer/details tests should exercise actual Swing components on the EDT using existing `BasePlatformTestCase` patterns.
- Update existing tests/helpers that construct `ModelPicker.Item`, `ModelItem`, or `ModelDto` as needed using default values to keep changes minimal.
## Risks And Constraints
- Full parity depends on metadata already present in CLI provider JSON. If a field is absent from the backend response for a provider, the details panel should omit that row rather than showing placeholders everywhere.
- Adding fields to shared serializable DTOs affects split-mode RPC compatibility. Keep new fields nullable/defaulted for safe deserialization.
- Avoid making provider loading fragile: optional preview metadata parsing must not throw on unexpected types.
- The popup is a lightweight `JBPopup`; changing size after showing may require recreating/repacking carefully. Prefer computing expanded/collapsed preferred size before show and updating the content with `revalidate()`/`repaint()` on toggle.
- Keep all Swing mutation on the EDT.
## Validation
- From `packages/kilo-jetbrains/`: run `./gradlew typecheck` or `bun run typecheck`.
- From `packages/kilo-jetbrains/`: run targeted tests for backend parser/serialization and frontend model picker tests, or `./gradlew test` if targeted Gradle filters are not straightforward.
- Manually verify in sandbox if feasible: `./gradlew runIde`, open model picker, confirm first open is collapsed, maximize doubles width and shows details, minimize returns to compact width, tooltip/cursor are correct, and the last state persists after closing/reopening the popup.
+2 -2
View File
@@ -53,7 +53,7 @@ All products are clients of the **CLI** (`packages/opencode/`), which contains t
In each VS Code extension host, one `KiloConnectionService` is created for the sidebar, every Kilo editor tab, and Agent Manager; it lazily starts and reuses one current `kilo serve` backend at a time. Agent Manager worktree sessions pass a directory context to this shared backend rather than starting one per worktree. State captured by the active service layer, such as Snapshot `trackState`, is shared across those requests; only directory-keyed `InstanceState` data is isolated.
Extension-specific settings should live in the Kilo extension settings, not default VS Code settings, unless they are intentionally VS Code-wide.
Extension-specific settings should live in the Kilo extension settings, not default VS Code settings, unless they are intentionally VS Code-wide. Experimental flags should follow existing flag patterns, not VS Code settings; they usually belong in the Kilo Experimental settings section.
## Package Instructions
@@ -155,7 +155,7 @@ Padding makes every content change rewrite the entire table, which blows up diff
## Changesets
User-facing changes (features, fixes, breaking changes) require a changeset file for release notes. Run `bunx changeset add` or manually create `.changeset/<slug>.md`. Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. See `.changeset/README.md` for details.
User-facing changes (features, fixes, breaking changes) require a changeset file for release notes. Prefer one concise changeset per PR, grouping related changes when possible. Run `bunx changeset add` or manually create `.changeset/<slug>.md`. Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. See `.changeset/README.md` for details.
Changeset descriptions appear directly in release notes and are read by end users. Keep them concise and feature-oriented — describe **what changed from the user's perspective**, not implementation details. Write in imperative mood (e.g. "Support exporting conversations as markdown" not "Add a new export handler that serializes session messages to .md files").
+3 -1
View File
@@ -126,9 +126,11 @@ Requires Java 21 (see [Prerequisites](#prerequisites)). From `packages/kilo-jetb
```bash
./gradlew typecheck # Compile-check all Kotlin sources
./gradlew test # Run all tests (backend + frontend)
./gradlew runIde # Launch sandboxed IntelliJ with the plugin
./gradlew --no-configuration-cache runIdeSplitMode # Launch local split-mode sandbox and prepare the CLI
```
Use `./gradlew runIde` only for a monolithic sandbox; prepare the CLI first with `bun run build --prepare-cli`.
Or via the root turbo filter to run only JetBrains checks from the repo root:
```bash
+2 -2
View File
@@ -1,5 +1,5 @@
<p align="center">
English | <a href="README.zh.md">简体中文</a> | <a href="README.zht.md">繁體中文</a> | <a href="README.ko.md">한국어</a> | <a href="README.de.md">Deutsch</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.it.md">Italiano</a> | <a href="README.da.md">Dansk</a> | <a href="README.ja.md">日本語</a> | <a href="README.pl.md">Polski</a> | <a href="README.ru.md">Русский</a> | <a href="README.bs.md">Bosanski</a> | <a href="README.ar.md">العربية</a> | <a href="README.no.md">Norsk</a> | <a href="README.br.md">Português (Brasil)</a> | <a href="README.th.md">ไทย</a> | <a href="README.tr.md">Türkçe</a> | <a href="README.uk.md">Українська</a> | <a href="README.bn.md">বাংলা</a> | <a href="README.gr.md">Ελληνικά</a> | <a href="README.vi.md">Tiếng Việt</a>
English | <a href="translations/README.zh.md">简体中文</a> | <a href="translations/README.zht.md">繁體中文</a> | <a href="translations/README.ko.md">한국어</a> | <a href="translations/README.de.md">Deutsch</a> | <a href="translations/README.es.md">Español</a> | <a href="translations/README.fr.md">Français</a> | <a href="translations/README.it.md">Italiano</a> | <a href="translations/README.da.md">Dansk</a> | <a href="translations/README.ja.md">日本語</a> | <a href="translations/README.pl.md">Polski</a> | <a href="translations/README.ru.md">Русский</a> | <a href="translations/README.bs.md">Bosanski</a> | <a href="translations/README.ar.md">العربية</a> | <a href="translations/README.no.md">Norsk</a> | <a href="translations/README.br.md">Português (Brasil)</a> | <a href="translations/README.th.md">ไทย</a> | <a href="translations/README.tr.md">Türkçe</a> | <a href="translations/README.uk.md">Українська</a> | <a href="translations/README.bn.md">বাংলা</a> | <a href="translations/README.gr.md">Ελληνικά</a> | <a href="translations/README.vi.md">Tiếng Việt</a>
</p>
<p align="center">
@@ -168,7 +168,7 @@ MIT. You're free to use, modify, and distribute this code, including commerciall
<details>
<summary>Where did Kilo CLI come from?</summary>
Kilo CLI is a fork of [OpenCode](https://github.com/Kilo-Org/kilocode), enhanced to work within the Kilo agentic engineering platform.
Kilo CLI is a fork of [OpenCode](https://github.com/anomalyco/opencode), enhanced to work within the Kilo agentic engineering platform.
</details>
+48 -30
View File
@@ -28,7 +28,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "7.3.54",
"version": "7.3.63",
"bin": {
"opencode": "./bin/opencode",
},
@@ -93,7 +93,7 @@
},
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -107,7 +107,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@effect/platform-node": "catalog:",
"effect": "catalog:",
@@ -120,7 +120,7 @@
},
"packages/kilo-console": {
"name": "@kilocode/kilo-console",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/kilo-web-ui": "workspace:*",
"@kilocode/sdk": "workspace:*",
@@ -142,7 +142,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -172,7 +172,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/anthropic": "3.0.71",
@@ -208,7 +208,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
"version": "7.3.54",
"version": "7.3.63",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -218,7 +218,7 @@
},
"packages/kilo-indexing": {
"name": "@kilocode/kilo-indexing",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
"@aws-sdk/credential-provider-ini": "3.972.31",
@@ -250,11 +250,25 @@
},
"packages/kilo-jetbrains": {
"name": "@kilocode/kilo-jetbrains",
"version": "7.3.54",
"version": "7.3.63",
},
"packages/kilo-memory": {
"name": "@kilocode/kilo-memory",
"version": "7.3.45",
"dependencies": {
"effect": "catalog:",
"zod": "catalog:",
},
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
},
},
"packages/kilo-sandbox": {
"name": "@kilocode/sandbox",
"version": "7.3.52",
"version": "7.3.63",
"dependencies": {
"effect": "catalog:",
},
@@ -267,7 +281,7 @@
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"posthog-node": "4.4.0",
@@ -281,7 +295,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "0.13.11",
@@ -308,7 +322,7 @@
"storybook": "10.2.10",
"storybook-solidjs-vite": "10.0.9",
"typescript": "catalog:",
"vite": "7.3.2",
"vite": "7.3.5",
"vite-plugin-solid": "2.11.10",
},
"peerDependencies": {
@@ -318,7 +332,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-gateway": "workspace:*",
@@ -327,6 +341,7 @@
"@kilocode/kilo-ui": "workspace:*",
"@kilocode/plugin": "workspace:*",
"@kilocode/sdk": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@pierre/diffs": "catalog:",
"@thisbeyond/solid-dnd": "0.7.5",
@@ -379,13 +394,13 @@
"ts-morph": "27.0.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0",
"vite": "7.3.2",
"vite": "7.3.5",
"vite-plugin-solid": "2.11.10",
},
},
"packages/kilo-web-ui": {
"name": "@kilocode/kilo-web-ui",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/kilo-ui": "workspace:*",
"@kobalte/core": "catalog:",
@@ -402,7 +417,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
@@ -420,7 +435,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
"version": "7.3.54",
"version": "7.3.63",
"bin": {
"kilo": "./bin/kilo",
"kilocode": "./bin/kilo",
@@ -570,7 +585,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"effect": "catalog:",
@@ -598,7 +613,7 @@
},
"packages/plugin-atomic-chat": {
"name": "@kilocode/plugin-atomic-chat",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/plugin": "workspace:*",
},
@@ -612,7 +627,7 @@
},
"packages/script": {
"name": "@opencode-ai/script",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"semver": "^7.6.3",
},
@@ -623,7 +638,7 @@
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -638,7 +653,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
"version": "7.3.54",
"version": "7.3.63",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -661,7 +676,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "7.3.54",
"version": "7.3.63",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
@@ -706,16 +721,16 @@
},
},
"trustedDependencies": [
"esbuild",
"tree-sitter-powershell",
"protobufjs",
"web-tree-sitter",
"tree-sitter-bash",
"esbuild",
"protobufjs",
],
"patchedDependencies": {
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
},
"overrides": {
"@effect/platform-node-shared": "4.0.0-beta.46",
@@ -736,6 +751,7 @@
"picomatch": ">=2.3.2",
"smol-toml": ">=1.6.1",
"solid-js": "catalog:",
"vite": "7.3.5",
},
"catalog": {
"@cloudflare/workers-types": "4.20251008.0",
@@ -789,7 +805,7 @@
"typescript": "5.8.2",
"ulid": "3.0.1",
"virtua": "0.42.3",
"vite": "7.3.2",
"vite": "7.3.5",
"vite-plugin-solid": "2.11.10",
"zod": "4.1.8",
},
@@ -1316,6 +1332,8 @@
"@kilocode/kilo-jetbrains": ["@kilocode/kilo-jetbrains@workspace:packages/kilo-jetbrains"],
"@kilocode/kilo-memory": ["@kilocode/kilo-memory@workspace:packages/kilo-memory"],
"@kilocode/kilo-telemetry": ["@kilocode/kilo-telemetry@workspace:packages/kilo-telemetry"],
"@kilocode/kilo-ui": ["@kilocode/kilo-ui@workspace:packages/kilo-ui"],
@@ -4268,7 +4286,7 @@
"virtua": ["virtua@0.42.3", "", { "peerDependencies": { "react": ">=16.14.0", "react-dom": ">=16.14.0", "solid-js": ">=1.0", "svelte": ">=5.0", "vue": ">=3.2" }, "optionalPeers": ["react", "react-dom", "solid-js", "svelte", "vue"] }, "sha512-5FoAKcEvh05qsUF97Yz42SWJ7bwnPExjUYHGuoxz1EUtfWtaOgXaRwnylJbDpA0QcH1rKvJ2qsGRi9MK1fpQbg=="],
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
"vite": ["vite@7.3.5", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww=="],
"vite-plugin-icons-spritesheet": ["vite-plugin-icons-spritesheet@3.0.1", "", { "dependencies": { "chalk": "^5.4.1", "glob": "^11.0.1", "node-html-parser": "^7.0.1", "tinyexec": "^0.3.2" }, "peerDependencies": { "vite": ">=5.2.0" } }, "sha512-Cr0+Z6wRMwSwKisWW9PHeTjqmQFv0jwRQQMc3YgAhAgZEe03j21el0P/CA31KN/L5eiL1LhR14VTXl96LetonA=="],
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-WYLCq59M4c6MsNjIeBmgV66v6LBvsTVGA1xV2oY935s=",
"aarch64-linux": "sha256-kpJ1SFCmBrhDwuJNJNoDU5NK6mDvEG4Yd41w9YJnsOs=",
"aarch64-darwin": "sha256-junSolEGOe+xUmPY4SAJGj+XKuytxvD0IWQWtLiaIEs=",
"x86_64-darwin": "sha256-p+3Jtb5CZexwx0UlhVeqeMr25R+jkjahFp9YTZYK0rE="
"x86_64-linux": "sha256-rYgS/DlyYz9Ll/3tJyNqNp7HhYlCoHQKMb6Z/youhXw=",
"aarch64-linux": "sha256-wLk3pF1djAH8zbvZSwPCH0CfnBwfAUwbUmZCAPNY14I=",
"aarch64-darwin": "sha256-M0uYr/AnDhaYNaIw4Lf7clvCS1fabALCbxC707BjKR4=",
"x86_64-darwin": "sha256-Rhjti+v4Of4fFoT3V85dYAla1T5T7+o4W8eTToPrN08="
}
}
+3 -2
View File
@@ -70,7 +70,7 @@
"solid-list": "0.3.0",
"tailwindcss": "4.1.11",
"virtua": "0.42.3",
"vite": "7.3.2",
"vite": "7.3.5",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -133,6 +133,7 @@
"@xmldom/xmldom": ">=0.8.12",
"smol-toml": ">=1.6.1",
"fastify": ">=5.8.3",
"vite": "7.3.5",
"diff": "8.0.4",
"dompurify": "3.4.2",
"happy-dom": ">=20.8.9",
@@ -148,6 +149,6 @@
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch"
},
"version": "7.3.54",
"version": "7.3.63",
"peerDependencies": {}
}
+2 -2
View File
@@ -1,13 +1,13 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.3.54",
"version": "7.3.63",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
"private": true,
"scripts": {
"test": "bun test",
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --dots --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
"typecheck": "tsgo --noEmit"
},
"bin": {
+4 -2
View File
@@ -2,6 +2,7 @@ import type * as Arr from "effect/Array"
import { NodeFileSystem, NodeSink, NodeStream } from "@effect/platform-node"
import * as NodePath from "@effect/platform-node/NodePath"
import { prepareCommand as prepareSandbox } from "@kilocode/sandbox" // kilocode_change
import { tap as tapStdio, tapped } from "./kilocode/stdio-tap" // kilocode_change - Bun drops buffered stdio on close
import * as Deferred from "effect/Deferred"
import * as Effect from "effect/Effect"
import * as Exit from "effect/Exit"
@@ -246,13 +247,13 @@ export const make = Effect.gen(function* () {
) => {
let stdout = proc.stdout
? NodeStream.fromReadable({
evaluate: () => proc.stdout!,
evaluate: () => tapped(proc, "stdout"), // kilocode_change - read the spawn-time tap
onError: (cause) => toPlatformError("fromReadable(stdout)", toError(cause), command),
})
: Stream.empty
let stderr = proc.stderr
? NodeStream.fromReadable({
evaluate: () => proc.stderr!,
evaluate: () => tapped(proc, "stderr"), // kilocode_change - read the spawn-time tap
onError: (cause) => toPlatformError("fromReadable(stderr)", toError(cause), command),
})
: Stream.empty
@@ -267,6 +268,7 @@ export const make = Effect.gen(function* () {
Effect.callback<readonly [NodeChildProcess.ChildProcess, ExitSignal], PlatformError.PlatformError>((resume) => {
const signal = Deferred.makeUnsafe<readonly [code: number | null, signal: NodeJS.Signals | null]>()
const proc = launch(command.command, command.args, opts)
tapStdio(proc) // kilocode_change - must run in the same tick as spawn
let end = false
let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined
proc.on("error", (err) => {
@@ -0,0 +1,151 @@
/**
* Soft per-session max-cost nudge.
*
* Alert (not hard-stop) the moment a session's cumulative cost crosses a
* whole-dollar threshold. The alert is non-blocking: the session keeps running
* while it is shown. Continue dismisses it (won't nag again for that limit);
* Stop is the surface's cue to abort.
*
* Cost signal: `SessionTable.cost` is written via direct SQL during message-part
* projection, so `session.updated` does NOT fire on cost change. The reliable
* signal is the per-assistant-message `cost`, summed here into a session total.
*/
export type MaxCostChoice = "continue" | "stop"
// Minimal shape of a message needed to aggregate session cost.
export interface MaxCostMessage {
id: string
sessionID: string
role?: string
cost?: number
}
export class MaxCostNudge {
readonly #msgs = new Map<string, { sid: string; cost: number }>()
readonly #totals = new Map<string, number>()
readonly #floors = new Map<string, number>()
readonly #alerted = new Map<string, Set<number>>() // sid -> limit values shown this run
readonly #acked = new Map<string, Set<number>>() // sid -> limit values continued past
#limit: number | undefined
// `> 0` rounds up to whole dollars; everything else disables (undefined).
static normalizeLimit(value: number | undefined | null): number | undefined {
if (value == null || !Number.isFinite(value) || value <= 0) return undefined
return Math.ceil(value)
}
// Format a cost as `$X.XX`, with 4 decimals below $1.
static formatCost(value: number): string {
return `$${value.toFixed(value < 1 ? 4 : 2)}`
}
setLimit(value: number | undefined | null): void {
this.#limit = MaxCostNudge.normalizeLimit(value)
}
get limit(): number | undefined {
return this.#limit
}
// Rebuild a session's total from a full message snapshot (seed on load).
resetMessageCosts(sid: string, messages: MaxCostMessage[]): number {
this.#dropMessages(sid)
let total = 0
for (const msg of messages) {
if (msg.sessionID !== sid || msg.role !== "assistant" || !Number.isFinite(msg.cost)) continue
const cost = msg.cost ?? 0
this.#msgs.set(msg.id, { sid, cost })
total += cost
}
this.#totals.set(sid, total)
return this.sessionCost(sid)
}
// Floor the session total with a direct cost signal (e.g. session.cost). Monotonic.
setSessionCost(sid: string, value: number): number {
if (Number.isFinite(value)) this.#floors.set(sid, Math.max(this.#floors.get(sid) ?? 0, value))
return this.sessionCost(sid)
}
// Record an assistant message cost (message.updated). Returns the session total.
updateMessageCost(sid: string, id: string, role: string | undefined, value: number | undefined): number {
if (role === "assistant") {
const prev = this.#msgs.get(id)
if (Number.isFinite(value)) {
if (prev && prev.sid !== sid) {
this.#totals.set(prev.sid, Math.max(0, (this.#totals.get(prev.sid) ?? 0) - prev.cost))
}
const before = prev?.sid === sid ? prev.cost : 0
const cost = value!
this.#msgs.set(id, { sid, cost })
this.#totals.set(sid, Math.max(0, (this.#totals.get(sid) ?? 0) - before + cost))
} else if (prev) {
// value became non-finite — drop the stale contribution
this.#totals.set(prev.sid, Math.max(0, (this.#totals.get(prev.sid) ?? 0) - prev.cost))
this.#msgs.delete(id)
}
}
return this.sessionCost(sid)
}
// Drop a message's contribution (message.removed).
removeMessageCost(id: string): void {
const prev = this.#msgs.get(id)
if (!prev) return
this.#msgs.delete(id)
this.#totals.set(prev.sid, Math.max(0, (this.#totals.get(prev.sid) ?? 0) - prev.cost))
}
sessionCost(sid: string): number {
return Math.max(this.#totals.get(sid) ?? 0, this.#floors.get(sid) ?? 0)
}
/**
* Decide whether to alert for `sid` now. Returns the limit + cost to show
* once per run, or undefined (below limit, already acknowledged, or already
* showing). Re-arm with {@link rearm} when the session runs again.
*/
check(sid: string): { limit: number; cost: number } | undefined {
const limit = this.#limit
if (limit === undefined) return undefined
const cost = this.sessionCost(sid)
if (cost < limit || this.#acked.get(sid)?.has(limit) || this.#alerted.get(sid)?.has(limit)) return undefined
this.#remember(this.#alerted, sid, limit)
return { limit, cost }
}
// Apply the user's choice. Continue suppresses re-alerts for the current limit.
resolve(sid: string, choice: MaxCostChoice, limit = this.#limit): void {
if (choice === "continue" && limit !== undefined) this.#remember(this.#acked, sid, limit)
}
// Re-arm alerts for a session that started running again.
rearm(sid: string): void {
this.#alerted.delete(sid)
}
// Forget all state for a deleted session.
onSessionDeleted(sid: string): void {
this.#dropMessages(sid)
this.#totals.delete(sid)
this.#floors.delete(sid)
this.#alerted.delete(sid)
this.#acked.delete(sid)
}
// Drop every message contribution belonging to a session.
#dropMessages(sid: string): void {
for (const [id, msg] of this.#msgs) {
if (msg.sid === sid) this.#msgs.delete(id)
}
}
// Record a limit value seen for a session.
#remember(map: Map<string, Set<number>>, sid: string, limit: number): void {
const seen = map.get(sid)
if (seen) seen.add(limit)
else map.set(sid, new Set([limit]))
}
}

Some files were not shown because too many files have changed in this diff Show More