Merge remote-tracking branch 'origin/main' into mark/direct-autocomplete-fim-providers

# Conflicts:
#	packages/kilo-gateway/src/server/routes.ts
#	packages/sdk/js/src/v2/gen/sdk.gen.ts
#	packages/sdk/js/src/v2/gen/types.gen.ts
This commit is contained in:
Mark IJbema
2026-05-26 09:43:57 +02:00
757 changed files with 70466 additions and 30105 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve the JetBrains model picker with sections, favorites, and smart filtering.
-5
View File
@@ -1,5 +0,0 @@
---
"@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.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Scale JetBrains chat transcript fonts from the editor font settings.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep the JetBrains model picker at the same row position when models are favorited or unfavorited.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Focus session history search when it opens so the first result can be resumed immediately.
-5
View File
@@ -1,5 +0,0 @@
---
"@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.
-5
View File
@@ -1,5 +0,0 @@
---
"@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.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Match VS Code-style reasoning and tool run rendering in JetBrains chat.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Center description text and remove the fixed-width cap from the empty session panel so recent sessions can stretch naturally.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Open existing JetBrains sessions scrolled to the latest message after history loads.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Remember the JetBrains session header metrics expansion preference and show file names in read/write timeline tooltips.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve JetBrains history navigation with Back support and a Show History action in recent sessions.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve the JetBrains history panel with native tabs, independent local and cloud search, and date-grouped cloud sessions.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve the JetBrains mode picker with descriptions, deprecated badges, and native IntelliJ selection behavior.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show JetBrains question prompts one question at a time with aligned native option rows.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep the JetBrains chat transcript pinned to bottom reliably while responses stream.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show a scroll-to-bottom button when JetBrains chat sessions are scrolled away from the latest message.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Render active question and permission prompts inside the scrollable JetBrains chat transcript.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support rebinding the JetBrains prompt Send action in the IntelliJ keymap.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show collapsible session metadata, context usage, token usage, and compaction controls in the JetBrains session header.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Add a native session history panel to the JetBrains plugin.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Control JetBrains chat transcript spacing from the session layout.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show loading immediately when opening JetBrains sessions without showing duplicate progress for new sessions.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Reduce redundant JetBrains chat scroll layout work while keeping bottom-follow behavior.
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show completed assistant steps in the JetBrains session timeline.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support stopping active JetBrains sessions from the prompt action button.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Size the JetBrains model picker from its rendered model rows instead of fixed popup dimensions.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support reasoning effort selection and resetting JetBrains chat model overrides to the default model.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Align checked and unchecked reasoning picker rows in the JetBrains plugin.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep the JetBrains session connection status above the prompt without shifting the chat layout.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Refine JetBrains session loading UI and settings actions.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Preserve JetBrains chat scroll position when reviewing earlier session output.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/kilo-jetbrains": patch
---
Avoid flickering transient loading and connection error states in JetBrains sessions.
+28 -5
View File
@@ -18,16 +18,39 @@ Some description of HOW you achieved it. Perhaps give a high level description o
## How to Test
### Manual/local verification
<!--
Describe local CLI, extension, or docs verification. Example:
- Opened the updated settings page and confirmed the new copy appears
-->
A straightforward scenario of how to test your changes will help reviewers that are not familiar with the part of the code that you are changing but want to see it in action. This section can include a description or step-by-step instructions of how to get to the state of v2 that your change affects.
-
A "How To Test" section can look something like this:
### Reviewer test steps
- Sign in with a user with tracks
- Activate `show_awesome_cat_gifs` feature (add `?feature.show_awesome_cat_gifs=1` to your URL)
- You should see a GIF with cats dancing
<!--
Provide steps reviewers can retrace. Example:
1. Sign in with a user with tracks
2. Activate `show_awesome_cat_gifs` feature by adding `?feature.show_awesome_cat_gifs=1` to your URL
3. Confirm you see a GIF with cats dancing
-->
-
### Blocked checks and substitute verification
<!--
If a relevant command could not be completed, name the command, explain the blocker or failure, and describe the substitute verification performed. Example:
- `bun run test:integration` could not complete because the required API key is unavailable; substitute verification was testing the affected flow manually in the development extension
-->
-
<!--
Every PR marked ready for review must include testing evidence. A bare "Not tested" or "N/A" answer is not sufficient. For each piece of evidence make clear if the test was executed by the agent or by a human
Docs-only, config-only, and similar changes still need concrete evidence. For examples, see [Testing Evidence for Pull Requests](https://kilo.ai/docs/contributing/development-environment#testing-evidence-for-pull-requests). Draft PRs may be incomplete until marked ready for review.
-->
## Get in Touch
@@ -1,170 +0,0 @@
name: daily-issues-recap
on:
schedule:
# Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
- cron: "0 23 * * *"
workflow_dispatch: # Allow manual trigger for testing
jobs:
daily-recap:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://kilo.ai/install | bash
- name: Generate daily issues recap
id: recap
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KILO_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow",
"gh search*": "allow"
},
"webfetch": "deny",
"edit": "deny",
"write": "deny"
}
run: |
# Get today's date range
TODAY=$(date -u +%Y-%m-%d)
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the Kilo repository.
TODAY'S DATE: ${TODAY}
STEP 1: Gather today's issues
Search for all OPEN issues created today (${TODAY}) using:
gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these:
adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
This recap is specifically for COMMUNITY (external) issues only.
STEP 2: Analyze and categorize
For each issue created today, categorize it:
**Severity Assessment:**
- CRITICAL: Crashes, data loss, security issues, blocks major functionality
- HIGH: Significant bugs affecting many users, important features broken
- MEDIUM: Bugs with workarounds, minor features broken
- LOW: Minor issues, cosmetic, nice-to-haves
**Activity Assessment:**
- Note issues with high comment counts or engagement
- Note issues from repeat reporters (check if author has filed before)
STEP 3: Cross-reference with existing issues
For issues that seem like feature requests or recurring bugs:
- Search for similar older issues to identify patterns
- Note if this is a frequently requested feature
- Identify any issues that are duplicates of long-standing requests
STEP 4: Generate the recap
Create a structured recap with these sections:
===DISCORD_START===
**Daily Issues Recap - ${TODAY}**
**Summary Stats**
- Total issues opened today: [count]
- By category: [bugs/features/questions]
**Critical/High Priority Issues**
[List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
**Most Active/Discussed**
[Issues with significant engagement or from active community members]
**Trending Topics**
[Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
**Duplicates & Related**
[Issues that relate to existing open issues]
===DISCORD_END===
STEP 5: Format for Discord
Format the recap as a Discord-compatible message:
- Use Discord markdown (**, __, etc.)
- BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
- Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
- Group related issues on single lines where possible
- Add emoji sparingly for critical items only
- HARD LIMIT: Keep under 1800 characters total
- Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
- Prioritize signal over completeness - only surface what matters
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
# Extract only the Discord message between markers
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
- name: Post to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
cat /tmp/recap.txt
exit 0
fi
# Read the recap
RECAP_RAW=$(cat /tmp/recap.txt)
RECAP_LENGTH=${#RECAP_RAW}
echo "Recap length: ${RECAP_LENGTH} chars"
# Function to post a message to Discord
post_to_discord() {
local msg="$1"
local content=$(echo "$msg" | jq -Rs '.')
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": ${content}}" \
"$DISCORD_WEBHOOK_URL"
sleep 1
}
# If under limit, send as single message
if [ "$RECAP_LENGTH" -le 1950 ]; then
post_to_discord "$RECAP_RAW"
else
echo "Splitting into multiple messages..."
remaining="$RECAP_RAW"
while [ ${#remaining} -gt 0 ]; do
if [ ${#remaining} -le 1950 ]; then
post_to_discord "$remaining"
break
else
chunk="${remaining:0:1900}"
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
chunk="${remaining:0:$last_newline}"
remaining="${remaining:$((last_newline+1))}"
else
chunk="${remaining:0:1900}"
remaining="${remaining:1900}"
fi
post_to_discord "$chunk"
fi
done
fi
echo "Posted daily recap to Discord"
@@ -1,173 +0,0 @@
name: daily-pr-recap
on:
schedule:
# Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
- cron: "0 22 * * *"
workflow_dispatch: # Allow manual trigger for testing
jobs:
pr-recap:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://kilo.ai/install | bash
- name: Generate daily PR recap
id: recap
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KILO_PERMISSION: |
{
"bash": {
"*": "deny",
"gh pr*": "allow",
"gh search*": "allow"
},
"webfetch": "deny",
"edit": "deny",
"write": "deny"
}
run: |
TODAY=$(date -u +%Y-%m-%d)
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the Kilo repository.
TODAY'S DATE: ${TODAY}
STEP 1: Gather PR data
Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}):
# Open PRs created today
gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
# Open PRs with activity today (updated today)
gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these:
adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
This recap is specifically for COMMUNITY (external) contributions only.
STEP 2: For high-activity PRs, check comment counts
For promising PRs, run:
gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
- copilot-pull-request-reviewer
- github-actions
STEP 3: Identify what matters (ONLY from today's PRs)
**Bug Fixes From Today:**
- PRs with 'fix' or 'bug' in title created/updated today
- Small bug fixes (< 100 lines changed) that are easy to review
- Bug fixes from community contributors
**High Activity Today:**
- PRs with significant human comments today (excluding bots listed above)
- PRs with back-and-forth discussion today
**Quick Wins:**
- Small PRs (< 50 lines) that are approved or nearly approved
- PRs that just need a final review
STEP 4: Generate the recap
Create a structured recap:
===DISCORD_START===
**Daily PR Recap - ${TODAY}**
**New PRs Today**
[PRs opened today - group by type: bug fixes, features, etc.]
**Active PRs Today**
[PRs with activity/updates today - significant discussion]
**Quick Wins**
[Small PRs ready to merge]
===DISCORD_END===
STEP 5: Format for Discord
- Use Discord markdown (**, __, etc.)
- BE EXTREMELY CONCISE - surface what we might miss
- Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
- Include PR author: [#1234](<url>) (@author)
- For bug fixes, add brief description of what it fixes
- Show line count for quick wins: \"(+15/-3 lines)\"
- HARD LIMIT: Keep under 1800 characters total
- Skip empty sections
- Focus on PRs that need human eyes
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt
# Extract only the Discord message between markers
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
- name: Post to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
cat /tmp/pr_recap.txt
exit 0
fi
# Read the recap
RECAP_RAW=$(cat /tmp/pr_recap.txt)
RECAP_LENGTH=${#RECAP_RAW}
echo "Recap length: ${RECAP_LENGTH} chars"
# Function to post a message to Discord
post_to_discord() {
local msg="$1"
local content=$(echo "$msg" | jq -Rs '.')
curl -s -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": ${content}}" \
"$DISCORD_WEBHOOK_URL"
sleep 1
}
# If under limit, send as single message
if [ "$RECAP_LENGTH" -le 1950 ]; then
post_to_discord "$RECAP_RAW"
else
echo "Splitting into multiple messages..."
remaining="$RECAP_RAW"
while [ ${#remaining} -gt 0 ]; do
if [ ${#remaining} -le 1950 ]; then
post_to_discord "$remaining"
break
else
chunk="${remaining:0:1900}"
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
chunk="${remaining:0:$last_newline}"
remaining="${remaining:$((last_newline+1))}"
else
chunk="${remaining:0:1900}"
remaining="${remaining:1900}"
fi
post_to_discord "$chunk"
fi
done
fi
echo "Posted daily PR recap to Discord"
+2 -1
View File
@@ -175,6 +175,7 @@ jobs:
cli_version: ${{ needs.version.outputs.version }}
secrets: inherit
# kilocode_change end
publish:
needs:
- version
@@ -208,6 +209,7 @@ jobs:
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false # kilocode_change
- name: Install @vscode/vsce
run: bun install -g @vscode/vsce
@@ -265,7 +267,6 @@ jobs:
GH_REPO: ${{ github.repository }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
VSCE_PAT: ${{ secrets.VSCE_TOKEN }}
OPENVSX_TOKEN: ${{ secrets.OVSX_TOKEN }}
+8 -1
View File
@@ -81,8 +81,15 @@ jobs:
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-
turbo-${{ runner.os }}-
# kilocode_change start
- name: Warm up Gradle wrapper
if: runner.os == 'Linux'
run: ./gradlew --version
working-directory: packages/kilo-jetbrains
# kilocode_change end
- name: Run unit tests
run: bun turbo test:ci --filter=!@kilocode/kilo-jetbrains # kilocode_change
run: bun turbo test:ci
env:
KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
+8
View File
@@ -17,5 +17,13 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
# kilocode_change start
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
# kilocode_change end
- name: Run typecheck
run: bun typecheck
+2
View File
@@ -34,9 +34,11 @@ a.out
target
.scripts
.direnv/
packages/kilo-jetbrains/build-tasks/.gradle/
# Local dev files
opencode-dev
.kilo-dev/
UPCOMING_CHANGELOG.md
logs/
*.bun-build
+69
View File
@@ -0,0 +1,69 @@
---
description: Review an upstream merge PR and write reports
---
Review $1.
Branch off so we have all the code for the reviewed PR. Then run the following reviews in parallel, using one subagent per review, and save each result in the named markdown file at the repository root. A human will read those files, so if in doubt add a finding for human verification.
When all subagents are done, commit the report files and create a draft PR with the reviewed PR branch as base.
Do not include exhaustive per-file checklists in the report files. Summarize the scope and methodology, then list only findings, notable non-findings, command outputs, and limitations.
## KILOCODE_CHANGE_MARKERS.md
Review carefully, file by file, whether we accidentally removed any `kilocode_change` marker.
Get the full list of files changed in the reviewed PR. Check each changed file. For each file, compare both our `main` branch and the upstream-merged version in the reviewed PR. Determine whether any marker removal, marker move, or Kilo-specific change makes sense, and comment on that.
Do not include a full "Files Checked" section. Mention the number of changed files checked, but only list files that have findings or need human verification.
## INFRASTRUCTURE_CHANGE.md
Review whether this PR adds, removes, or changes any infrastructure, such as GitHub Actions, CI config, release/deploy scripts, Docker/build infrastructure, package manager/workspace infrastructure, repository automation, issue templates, changelog automation, or generated SDK/build automation.
We want to merge upstream code but keep our own infrastructure, so flag anything infrastructure-related. When in doubt, add a finding and say a human should check manually.
Do not include a full "Files Checked" section. Only list infrastructure-relevant files and findings.
## OPENCODE_MENTIONS.md
Check whether the merge now mentions OpenCode somewhere user-facing instead of Kilo, or links to OpenCode web properties.
Focus on UI strings, docs, help text, package metadata shown to users, URLs, CLI output, config docs, generated SDK/OpenAPI descriptions, and error messages.
## UNNECESSARY_MARKERS.md
Check whether any merged files now use `kilocode_change` markers without any actual difference to upstream.
Use `script/upstream/find-reset-candidates.ts --dry-run` to see whether any files changed in the PR are actually now identical to upstream. If you find candidates, verify them with `script/upstream/reset-to-upstream.ts --dry-run`.
## BROKEN_PIPELINE_CHAINS.md
Review this PR for broken end-to-end chains where our custom functionality requires changes across multiple files or layers, but the merge may have removed or altered an intermediate step. The code may still compile, so these issues can be silent.
Look for cases like:
- A parameter that is set but never read.
- A field that is populated but never passed through.
- An event that is emitted but no longer handled.
- A config option that is defined but never propagated to where it is used.
- A type definition extended on one side but not consumed on the other.
For each `kilocode_change` in this PR, trace the full chain: where the value or behavior is introduced, where it needs to flow through, and where it is ultimately consumed. Verify every link in the chain still exists after the merge.
Pay special attention to:
- Props or parameters passed through multiple component or function layers.
- Values written to state, context, or storage that are read elsewhere.
- Message types, events, or IPC handlers where sender and receiver must match.
- Configuration or feature flags defined in one place and checked in another.
- Type definitions extended on one side but consumed on the other.
When in doubt, add a finding. A human will verify it. Compiling code is not proof the chain is intact.
## TESTS.md
Check whether this PR removed any Kilo-specific tests.
Kilo-specific tests may live in paths containing `kilo` or `kilocode`, or may include Kilo-specific assertions, fixtures, or `kilocode_change` markers.
+1 -1
View File
@@ -1 +1 @@
v1.14.33
v1.14.41
+19 -76
View File
@@ -1,7 +1,7 @@
---
mode: primary
hidden: true
model: kilo/minimax/minimax-m2.5
model: openai/gpt-5-nano
color: "#44BA81"
tools:
"*": false
@@ -14,95 +14,38 @@ Use your github-triage tool to triage issues.
This file is the source of truth for ownership/routing rules.
## Labels
Assign issues by choosing the team with the strongest overlap. The github-triage tool will assign a random member from that team.
### windows
Do not add labels to issues. Only assign an owner.
Use for any issue that mentions Windows (the OS). Be sure they are saying that they are on Windows.
When calling github-triage, pass one of these team values: tui, desktop_web, core, inference, windows, jetbrains, extension
- Use if they mention WSL too
## Teams
#### perf
### TUI
Performance-related issues:
Terminal UI issues, including rendering, keybindings, scrolling, terminal compatibility, SSH behavior, crashes in the TUI, and low-level TUI performance.
- Slow performance
- High RAM usage
- High CPU usage
### Desktop / Web
**Only** add if it's likely a RAM or CPU issue. **Do not** add for LLM slowness.
Desktop application and browser-based app issues, including `opencode web`, desktop-specific UI behavior, packaging, and web view problems.
#### nix
### Core
**Only** add if the issue explicitly mentions nix.
Core opencode server and harness issues, including sqlite, snapshots, memory, API behavior, agent context construction, tool execution, provider integrations, model behavior, documentation, and larger architectural features.
If the issue does not mention nix, do not add nix.
### Inference
If the issue mentions nix, assign to `catrielmuller`.
Kilo Provider, Provider implementation, and billing issues.
#### core
### Windows
Use for core server issues in `packages/opencode/`, excluding `packages/opencode/src/cli/cmd/tui/`.
Windows-specific issues, including native Windows behavior, WSL interactions, path handling, shell compatibility, and installation or runtime problems that only happen on Windows.
Examples:
### Jetbrains
- LSP server behavior
- Harness behavior (agent + tools)
- Feature requests for server behavior
- Agent context construction
- API endpoints
- Provider integration issues
- New, broken, or poor-quality models
Jetbrains extension related issues.
#### vscode
### Extension
Use for issues related to the VS Code extension in `packages/kilo-vscode/`.
#### gateway
Use for issues related to the Kilo Gateway in `packages/kilo-gateway/`.
When assigning to people here are the following rules:
Nix:
ONLY assign if the issue will have the "nix" label.
- catrielmuller
Models / Providers:
Use for issues about model quality, provider integrations, or broken/new models.
- chrarnoldus
Cloud Agents:
Use for issues about cloud agent behavior or infrastructure.
- pandemicsyn
- eshurakov
Core (`packages/opencode/...`):
- kevinvandijk
- marius-kilocode
- catrielmuller
VSCode Extension (`packages/kilo-vscode/...`):
- markijbema
Kilo Gateway (`packages/kilo-gateway/...`):
- jrf0110
Windows:
- catrielmuller (assign any issue that mentions Windows or is likely Windows-specific)
Determinism rules:
- If "nix" label is added but title + body does not mention nix/nixos, the tool will drop "nix"
- If title + body mentions nix/nixos, assign to `catrielmuller`
- If "vscode" label is added, assign to `markijbema`
- If "gateway" label is added, assign to `jrf0110`
In all other cases, choose the team/section with the most overlap with the issue and assign a member from that team at random.
VSCode Extension related issues.
+4 -1
View File
@@ -18,9 +18,12 @@ Do not use `git log` or author metadata when deciding attribution.
Rules:
- Write the final file with sections in this order:
- Write the final file with release sections in this order:
`## Core`, `## TUI`, `## Desktop`, `## SDK`, `## Extensions`
- Only include sections that have at least one notable entry
- Within each release section, keep bug fixes grouped under `### Bugfixes`
- Keep other notable entries under `### Improvements` when a section has bug fixes too
- Omit empty subsections
- Keep one bullet per commit you keep
- Skip commits that are entirely internal, CI, tests, refactors, or otherwise not user-facing
- Start each bullet with a capital letter
+22 -64
View File
@@ -1,15 +1,16 @@
/// <reference path="../env.d.ts" />
import { tool } from "@kilocode/plugin"
const TEAM = {
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
zen: ["fwang", "MrMushrooooom"],
tui: ["kommander", "rekram1-node", "simonklee"],
core: ["kitlangton", "rekram1-node", "jlongster"],
docs: ["R44VC0RP"],
windows: ["Hona"],
} as const
const ASSIGNEES = [...new Set(Object.values(TEAM).flat())]
// kilocode_change start
const TEAM = {
tui: ["catrielmuller"],
desktop_web: ["markijbema"],
core: ["markijbema", "marius-kilocode", "catrielmuller", "chrarnoldus", "imanolmzd-svg"],
jetbrains: ["kirillk"],
inference: ["chrarnoldus", "markijbema"],
windows: ["catrielmuller"],
} as const
// kilocode_change end
function pick<T>(items: readonly T[]) {
return items[Math.floor(Math.random() * items.length)]!
@@ -38,70 +39,27 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
}
export default tool({
description: `Use this tool to assign and/or label a GitHub issue.
description: `Use this tool to assign a GitHub issue.
Choose labels and assignee using the current triage policy and ownership rules.
Pick the most fitting labels for the issue and assign one owner.
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.`,
Provide the team that should own the issue. This tool picks a random assignee from that team and does not apply labels.`,
args: {
assignee: tool.schema
.enum(["thdxr", "adamdotdevin", "rekram1-node", "fwang", "jayair", "kommander"])
.describe("The username of the assignee")
.default("rekram1-node"),
labels: tool.schema
.array(tool.schema.enum(["nix", "opentui", "perf", "desktop", "zen", "docs", "windows"]))
.describe("The labels(s) to add to the issue")
.default([]),
team: tool.schema
.enum(Object.keys(TEAM) as [keyof typeof TEAM, ...(keyof typeof TEAM)[]])
.describe("The owning team"),
},
async execute(args) {
const issue = getIssueNumber()
// const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
const owner = "anomalyco"
const repo = "opencode"
// kilocode_change start
const owner = "Kilo-Org"
const repo = "kilocode"
// kilocode_change end
const assignee = pick(TEAM[args.team])
const results: string[] = []
if (args.assignee === "adamdotdevin" && !args.labels.includes("desktop")) {
throw new Error("Only desktop issues should be assigned to adamdotdevin")
}
if (args.assignee === "fwang" && !args.labels.includes("zen")) {
throw new Error("Only zen issues should be assigned to fwang")
}
if (args.assignee === "kommander" && !args.labels.includes("opentui")) {
throw new Error("Only opentui issues should be assigned to kommander")
}
// await octokit.rest.issues.addAssignees({
// owner,
// repo,
// issue_number: issue,
// assignees: [args.assignee],
// })
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
method: "POST",
body: JSON.stringify({ assignees: [args.assignee] }),
body: JSON.stringify({ assignees: [assignee] }),
})
results.push(`Assigned @${args.assignee} to issue #${issue}`)
const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label))
if (labels.length > 0) {
// await octokit.rest.issues.addLabels({
// owner,
// repo,
// issue_number: issue,
// labels,
// })
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, {
method: "POST",
body: JSON.stringify({ labels }),
})
results.push(`Added labels: ${args.labels.join(", ")}`)
}
return results.join("\n")
return `Assigned @${assignee} from ${args.team} to issue #${issue}`
},
})
+2 -1
View File
@@ -12,7 +12,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang
- **Dev**: `bun run dev` (runs from root) or `bun run --cwd packages/opencode --conditions=browser src/index.ts`
- **Dev with params**: `bun dev -- help`
- **Extension**: `bun run extension` (build + launch VS Code with the extension in dev mode). Pass `--no-build` to skip the build.
- **Typecheck**: `bun turbo typecheck` (uses `tsgo`, not `tsc`)
- **Typecheck**: `bun turbo typecheck` (uses `tsgo`, not `tsc`). Includes the JetBrains plugin — requires Java 21. Check with `java -version` before running. If missing, install via SDKMAN: `sdk install java 21-tem && sdk use java 21-tem`. If SDKMAN is not installed, see https://sdkman.io/install.
- **Test**: `bun test` from `packages/opencode/` (NOT from root -- root blocks tests)
- **Single test**: `bun test ./test/tool/tool-define.test.ts` from `packages/opencode/`
- **CLI build artifact size check**: after `bun run script/build.ts --single --skip-install` in `packages/opencode/`, use `du -h dist/*/*/bin/kilo` (scoped package output lives under `dist/@kilocode/`)
@@ -34,6 +34,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 |
| JetBrains plugin | From `packages/kilo-jetbrains/`: `./gradlew typecheck`, `./gradlew test`. Requires Java 21 — check first with `java -version`. Install via SDKMAN if missing: `sdk install java 21-tem && sdk use java 21-tem`. |
| 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.
+118 -2
View File
@@ -14,16 +14,101 @@ There are lots of ways to contribute to the project:
The Kilo Community is [on Discord](https://kilo.ai/discord).
## Prerequisites
- **Bun 1.3.13+** — required for all packages.
- **Java 21** — required by the JetBrains plugin. The root `bun turbo typecheck` and `bun turbo test:ci` commands include `@kilocode/kilo-jetbrains` and will fail without Java 21.
The preferred way to install Java is via [SDKMAN](https://sdkman.io/install):
```bash
# Install SDKMAN (if not already installed)
curl -s "https://get.sdkman.io" | bash
# Install and activate Java 21 (Eclipse Temurin)
sdk install java 21-tem
sdk use java 21-tem
# Verify
java -version
```
If you don't plan to work on the JetBrains plugin, you can still run non-JetBrains checks directly:
```bash
bun turbo typecheck --filter=!@kilocode/kilo-jetbrains
```
## Developing Kilo CLI
- **Requirements:** Bun 1.3.13+
- Install dependencies and start the dev server from the repo root:
- **Requirements:** Bun 1.3.13+, Java 21 (see [Prerequisites](#prerequisites) above)
- Install dependencies and start the CLI from the repo root:
```bash
bun install
bun dev
```
`bun dev` and `bun run dev` both run the local CLI. For the VS Code extension, use `bun run extension`.
## Common Checks
From the repo root:
```bash
bun install
bun run lint
bun run typecheck
```
`bun run typecheck` wraps `bun turbo typecheck`. Use `bun turbo typecheck --force` if you need to bypass the Turbo cache.
Do **not** run `bun test` from the repo root. The root test script intentionally exits with failure so tests run from the package that owns them.
### CLI checks
From `packages/opencode/`:
```bash
bun run typecheck
bun test
bun test ./path/to/file.test.ts
```
For backend/API validation, see [`TESTING.md`](./TESTING.md). It covers starting the local backend with `bun dev serve` and making `curl` requests against it. After changing server endpoints in `packages/opencode/src/server/`, run `./script/generate.ts` from the repo root to regenerate `packages/sdk/js/`.
### VS Code extension checks
From `packages/kilo-vscode/`:
```bash
bun run typecheck
bun run lint
bun run test:unit
bun run test
bun run compile
bun run package
```
### Documentation checks
From the repo root:
```bash
bun run --filter @kilocode/kilo-docs test
bun run --filter @kilocode/kilo-docs build
bun run --filter @kilocode/kilo-docs dev
```
For manual docs validation, run the docs site locally, preview the affected page, and check changed links and rendered content.
### Guardrails
- User-facing changes usually need a changeset (`bunx changeset add` or a file under `.changeset/`).
- After changing server endpoints, regenerate the SDK with `./script/generate.ts`.
- After adding or changing guarded URLs in `packages/kilo-vscode/`, `packages/kilo-vscode/webview-ui/`, or `packages/opencode/src/`, run `bun run script/extract-source-links.ts` from the repo root.
- When editing shared `packages/opencode/` files, keep Kilo changes small and mark Kilo-only edits with `// kilocode_change` for a single line or `// kilocode_change start` / `// kilocode_change end` for a block. Do not add these markers inside `kilocode`-named paths.
### Developing the VS Code Extension
Build and launch the extension in an isolated VS Code instance:
@@ -34,6 +119,23 @@ bun run extension # Build + launch in dev mode
This auto-detects VS Code on macOS, Linux, and Windows. Override with `--app-path PATH` or `VSCODE_EXEC_PATH`. Use `--insiders` to prefer Insiders, `--workspace PATH` to open a specific folder, or `--clean` to reset cached state.
### Developing the JetBrains Plugin
Requires Java 21 (see [Prerequisites](#prerequisites)). From `packages/kilo-jetbrains/`:
```bash
./gradlew typecheck # Compile-check all Kotlin sources
./gradlew test # Run all tests (backend + frontend)
./gradlew runIde # Launch sandboxed IntelliJ with the plugin
```
Or via the root turbo filter to run only JetBrains checks from the repo root:
```bash
bun turbo typecheck --filter=@kilocode/kilo-jetbrains
bun turbo test:ci --filter=@kilocode/kilo-jetbrains
```
### Running against a different directory
By default, `bun dev` runs Kilo CLI in the `packages/opencode` directory. To run it against a different directory or repository:
@@ -149,6 +251,20 @@ Current required fields by issue type:
- **UI Changes:** Include screenshots or videos (before/after).
- **Logic Changes:** Explain how you verified it works.
### Testing Evidence
Every PR marked ready for review must include testing evidence. A bare `Not tested` or `N/A` answer is not sufficient.
Choose checks that match the files touched. Include command results and manual/local verification; for visual CLI or extension changes, include screenshots or videos. Docs-only, config-only, and similar changes still need concrete evidence, such as a relevant command check or preview.
If you cannot complete a relevant command, include all of the following in the PR:
- The command you attempted or would normally run
- The blocker or failure that prevented completion
- The substitute verification you performed instead
See [Testing Evidence for Pull Requests](packages/kilo-docs/pages/contributing/development-environment.md#testing-evidence-for-pull-requests) for more examples. Agent limitations, local resource constraints, OOM constraints, or an agent prompt that says to skip tests do not waive this requirement. Draft PRs may be incomplete until they are marked ready for review. Maintainers may still defer or close review at their discretion.
## Issue First Policy
All pull requests must reference an existing issue.
+149 -38
View File
@@ -28,12 +28,12 @@
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",
"turbo": "2.8.13",
"turbo": "2.9.14",
},
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "7.3.1",
"version": "7.3.10",
"bin": {
"opencode": "./bin/opencode",
},
@@ -68,7 +68,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -98,7 +98,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/anthropic": "3.0.71",
@@ -134,7 +134,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
"version": "7.3.1",
"version": "7.3.10",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -144,7 +144,7 @@
},
"packages/kilo-indexing": {
"name": "@kilocode/kilo-indexing",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
"@aws-sdk/credential-provider-ini": "3.972.31",
@@ -154,6 +154,7 @@
"@qdrant/js-client-rest": "1.17.0",
"async-mutex": "0.5.0",
"chokidar": "4.0.3",
"effect": "catalog:",
"glob": "13.0.6",
"hono": "catalog:",
"hono-openapi": "catalog:",
@@ -175,10 +176,11 @@
},
"packages/kilo-jetbrains": {
"name": "@kilocode/kilo-jetbrains",
"version": "7.3.10",
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"posthog-node": "4.4.0",
@@ -192,7 +194,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "0.13.11",
@@ -229,7 +231,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-gateway": "workspace:*",
@@ -292,7 +294,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
"version": "7.3.1",
"version": "7.3.10",
"bin": {
"kilo": "./bin/kilo",
"kilocode": "./bin/kilo",
@@ -300,7 +302,7 @@
"dependencies": {
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.16.1",
"@agentclientprotocol/sdk": "0.21.0",
"@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.96",
"@ai-sdk/anthropic": "3.0.71",
@@ -450,7 +452,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"effect": "catalog:",
@@ -475,7 +477,7 @@
},
"packages/script": {
"name": "@opencode-ai/script",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"semver": "^7.6.3",
},
@@ -486,7 +488,7 @@
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -501,7 +503,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
"version": "7.3.1",
"version": "7.3.10",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -524,7 +526,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "7.3.1",
"version": "7.3.10",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
@@ -574,6 +576,13 @@
},
},
},
"trustedDependencies": [
"esbuild",
"tree-sitter-powershell",
"protobufjs",
"web-tree-sitter",
"tree-sitter-bash",
],
"patchedDependencies": {
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
@@ -606,7 +615,7 @@
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opentui/core": "0.2.2",
"@opentui/solid": "0.2.2",
"@pierre/diffs": "1.1.0-beta.18",
"@pierre/diffs": "1.1.22",
"@playwright/test": "1.59.1",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4",
@@ -618,7 +627,7 @@
"@types/bun": "1.3.12",
"@types/cross-spawn": "6.0.6",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/node": "24.12.2",
"@types/semver": "7.7.1",
"@typescript/native-preview": "7.0.0-dev.20260316.1",
"ai": "6.0.168",
@@ -627,7 +636,7 @@
"dompurify": "3.4.2",
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
"effect": "4.0.0-beta.57",
"effect": "4.0.0-beta.59",
"fuzzysort": "3.1.0",
"hono": "4.12.12",
"hono-openapi": "1.1.2",
@@ -664,7 +673,7 @@
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.16.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.21.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw=="],
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
@@ -1636,9 +1645,9 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.18", "", { "dependencies": { "@pierre/theme": "0.0.22", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-7ZF3YD9fxdbYsPnltz5cUqHacN7ztp8RX/fJLxwv8wIEORpP4+7dHz1h/qx3o4EW2xUrIhmbM8ImywLasB787Q=="],
"@pierre/diffs": ["@pierre/diffs@1.1.22", "", { "dependencies": { "@pierre/theme": "0.0.28", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1Iv7kdl6OABFCd1n2HQbGUiRHouXPaHoIjcb7Lwg8zeJKY5ph+cESFcGEyIiwW0NCbKGtYS2bTnXmI+Eze5dwg=="],
"@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="],
"@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
@@ -2020,6 +2029,18 @@
"@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="],
"@turbo/darwin-64": ["@turbo/darwin-64@2.9.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A=="],
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d23147mC9BsCPA9mJ0h/ubcpbRgcJBXbcG3+Vq7YLhjz3IXuvQsJ1UXH8f4MD76ZjJ4m/E4aRdJV+MW88CDfbw=="],
"@turbo/linux-64": ["@turbo/linux-64@2.9.14", "", { "os": "linux", "cpu": "x64" }, "sha512-P3ZKB5tuUDdDQWuAsACGUR1qv9W7BNWxdxqVJ0kZNuNNPRaVYTPPikLcp79+GiEcW3npsR+KyP38lnQiBc5aSA=="],
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZRTlzcUMrrPv9ZuDzRF9n60Ym13bKeG9jDB8WjxyLhWNzV+AJQN+zdpIk3NJYf2zQsGUm1mNar2P0elRzLw25g=="],
"@turbo/windows-64": ["@turbo/windows-64@2.9.14", "", { "os": "win32", "cpu": "x64" }, "sha512-exanwN6sIduZwykYeiTQj8kCmOhazP5WOz3bvXMcYtjhL6Z3iRWLewKrXCBq0bqwSP3iBMb/AerRCnHI4lx46A=="],
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
@@ -2144,7 +2165,7 @@
"@types/mssql": ["@types/mssql@9.1.11", "", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-vcujgrDbDezCxNDO4KY6gjwduLYOKfrexpRUwhoysRvcXZ3+IgZ/PMYFDgh8c3cQIxZ6skAwYo+H6ibMrBWPjQ=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="],
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
@@ -2824,7 +2845,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="],
"effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.340", "", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="],
@@ -4288,19 +4309,7 @@
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="],
"turbo-darwin-64": ["turbo-darwin-64@2.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ=="],
"turbo-linux-64": ["turbo-linux-64@2.8.13", "", { "os": "linux", "cpu": "x64" }, "sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.8.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg=="],
"turbo-windows-64": ["turbo-windows-64@2.8.13", "", { "os": "win32", "cpu": "x64" }, "sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.8.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA=="],
"turbo": ["turbo@2.9.14", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.14", "@turbo/darwin-arm64": "2.9.14", "@turbo/linux-64": "2.9.14", "@turbo/linux-arm64": "2.9.14", "@turbo/windows-64": "2.9.14", "@turbo/windows-arm64": "2.9.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw=="],
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
@@ -4330,7 +4339,7 @@
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
@@ -4544,6 +4553,8 @@
"@antfu/install-pkg/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
"@anthropic-ai/sdk/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
@@ -4648,6 +4659,8 @@
"@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"@manypkg/find-root/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
@@ -4704,6 +4717,8 @@
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@opencode-ai/plugin/effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="],
"@opencode-ai/storybook/@storybook/addon-a11y": ["@storybook/addon-a11y@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-5k6lpgfIeLxvNhE8v3wEzdiu73ONKjF4gmH1AHvfqYd8kIVzQJai0KCDxgvqNncXHQhIWkaf1fg6+9hKaYJyaw=="],
"@opencode-ai/storybook/@storybook/addon-docs": ["@storybook/addon-docs@10.3.5", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.3.5", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.3.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA=="],
@@ -4750,6 +4765,10 @@
"@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="],
"@standard-community/standard-json/effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="],
"@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.57", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g=="],
"@storybook/addon-links/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="],
"@storybook/addon-onboarding/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="],
@@ -4790,6 +4809,30 @@
"@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@types/cacache/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/cross-spawn/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/mssql/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/node-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/npm-registry-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/npmcli__arborist/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/npmlog/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/pacote/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/qrcode/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/readable-stream/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/ssri/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/ws/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
"@vscode/ripgrep/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
@@ -4826,6 +4869,8 @@
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="],
"apache-arrow/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
@@ -4860,6 +4905,8 @@
"buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"bun-types/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
@@ -4952,6 +4999,8 @@
"gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
"image-q/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"isomorphic-git/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -5074,6 +5123,8 @@
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
"protobufjs/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
@@ -5140,6 +5191,8 @@
"tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"tedious/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"test-exclude/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@@ -5198,6 +5251,8 @@
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -5236,10 +5291,14 @@
"@kilocode/kilo-gateway/@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.9", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.8" }, "optionalPeers": ["solid-js"] }, "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw=="],
"@manypkg/find-root/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"@morphllm/morphsdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
"@morphllm/morphsdk/openai/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
@@ -5274,6 +5333,10 @@
"@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
"@opencode-ai/plugin/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@opencode-ai/plugin/effect/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
"@opencode-ai/storybook/@storybook/addon-docs/@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.5", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.5", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw=="],
"@opencode-ai/storybook/@storybook/addon-docs/@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.3.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.5" } }, "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g=="],
@@ -5292,6 +5355,14 @@
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@standard-community/standard-json/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-community/standard-json/effect/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-community/standard-openapi/effect/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
"@storybook/addon-links/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
"@storybook/addon-onboarding/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
@@ -5334,6 +5405,30 @@
"@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@types/cacache/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/cross-spawn/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/mssql/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/npm-registry-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/npmcli__arborist/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/npmlog/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/pacote/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/qrcode/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/readable-stream/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/ssri/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@vscode/ripgrep/yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"@vscode/test-cli/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -5376,6 +5471,8 @@
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"apache-arrow/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
@@ -5406,6 +5503,8 @@
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"c8/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
@@ -5510,10 +5609,14 @@
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"image-q/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"kilo-code/openai/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
@@ -5576,6 +5679,8 @@
"posthog-js/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.7.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ=="],
"protobufjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
"qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
@@ -5606,6 +5711,8 @@
"tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"tedious/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"test-exclude/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"test-exclude/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
@@ -5638,6 +5745,8 @@
"@morphllm/morphsdk/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
"@morphllm/morphsdk/openai/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
@@ -5750,6 +5859,8 @@
"gray-matter/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"kilo-code/openai/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"mocha/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"mocha/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+4
View File
@@ -348,6 +348,10 @@ download_and_install() {
# kilocode_change start
mv "$tmp_dir/kilo" "$INSTALL_DIR"
rm -rf "${INSTALL_DIR}/tree-sitter"
if [ -d "$tmp_dir/tree-sitter" ]; then
mv "$tmp_dir/tree-sitter" "$INSTALL_DIR"
fi
chmod 755 "${INSTALL_DIR}/kilo"
# kilocode_change end
rm -rf "$tmp_dir"
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-NZqcbv0MX1WV/BkAT1poERcDEFiA4s1Gsf7W4p5wjlk=",
"aarch64-linux": "sha256-WSl5114O35I9Fae7+BbYXWznKSApPXN/Ripjqr6NO5U=",
"aarch64-darwin": "sha256-VemzuMnNbZo7vgHS5UiHVFGPVy9VL3gVwm+cw6ij1cg=",
"x86_64-darwin": "sha256-tg2Zt8qE5EZWCVNLxKzXtaygvzAkO0IFysMfFhIfZBs="
"x86_64-linux": "sha256-EbXkvlbexL1Gyy4vvGG5j+JWjv7SsVk6T5YnDfo3AQY=",
"aarch64-linux": "sha256-+MuJE4XGr0dArD/HuozaoK9Oymfgvx1CisE/Sm4ZstY=",
"aarch64-darwin": "sha256-Qdk+tZLydGld471ApL1cxfd85QQNFelTfqq7uAznfK4=",
"x86_64-darwin": "sha256-Fo6W65MfAcXaulBGCaE7+GfHtq3VVJyVDJpIENvR7lQ="
}
}
+19 -8
View File
@@ -7,16 +7,17 @@
"packageManager": "bun@1.3.13",
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev-setup": "bun run --cwd packages/opencode --conditions=browser src/index.ts dev-setup",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"typecheck": "bun turbo typecheck --filter=!@kilocode/kilo-jetbrains",
"typecheck": "bun turbo typecheck",
"test:ci": "bun turbo test:ci",
"postinstall": "bun run --cwd packages/opencode fix-node-pty && bun run script/setup-git.ts",
"prepare": "husky",
"random": "echo 'Random script'",
"hello": "echo 'Hello World!'",
"test": "echo 'do not run tests from root' && exit 1",
"extension": "bun --cwd packages/kilo-vscode script/launch.ts"
"extension": "bun --cwd packages/kilo-vscode script/launch.ts",
"dev-setup": "bun run --cwd packages/opencode --conditions=browser src/index.ts dev-setup"
},
"workspaces": {
"packages": [
@@ -36,13 +37,13 @@
"ulid": "3.0.1",
"@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1",
"@types/node": "22.13.9",
"@types/node": "24.12.2",
"@types/semver": "7.7.1",
"@tsconfig/node22": "22.0.2",
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.1.0-beta.18",
"@pierre/diffs": "1.1.22",
"opentui-spinner": "0.0.6",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
@@ -50,7 +51,7 @@
"dompurify": "3.4.2",
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
"effect": "4.0.0-beta.57",
"effect": "4.0.0-beta.59",
"ai": "6.0.168",
"cross-spawn": "7.0.6",
"hono": "4.12.12",
@@ -91,7 +92,7 @@
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",
"turbo": "2.8.13",
"turbo": "2.9.14",
"@types/bun": "catalog:",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.27.10"
@@ -114,6 +115,16 @@
"semi": false,
"printWidth": 120
},
"trustedDependencies": [
"esbuild",
"node-pty",
"protobufjs",
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-powershell",
"web-tree-sitter",
"electron"
],
"overrides": {
"@types/bun": "catalog:",
"@types/node": "catalog:",
@@ -135,6 +146,6 @@
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch"
},
"version": "7.3.1",
"version": "7.3.10",
"peerDependencies": {}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "7.3.1",
"version": "7.3.10",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+8
View File
@@ -46,6 +46,7 @@ export namespace AppFileSystem {
readonly isDir: (path: string) => Effect.Effect<boolean>
readonly isFile: (path: string) => Effect.Effect<boolean>
readonly existsSafe: (path: string) => Effect.Effect<boolean>
readonly readFileStringSafe: (path: string) => Effect.Effect<string | undefined, Error>
readonly readJson: (path: string) => Effect.Effect<unknown, Error>
readonly writeJson: (path: string, data: unknown, mode?: number) => Effect.Effect<void, Error>
readonly ensureDir: (path: string) => Effect.Effect<void, Error>
@@ -69,6 +70,12 @@ export namespace AppFileSystem {
return yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false))
})
const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path: string) {
return yield* fs
.readFileString(path)
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)))
})
const isDir = Effect.fn("FileSystem.isDir")(function* (path: string) {
const info = yield* fs.stat(path).pipe(Effect.catch(() => Effect.void))
return info?.type === "Directory"
@@ -195,6 +202,7 @@ export namespace AppFileSystem {
return Service.of({
...fs,
existsSafe,
readFileStringSafe,
isDir,
isFile,
readDirectoryEntries,
+15 -2
View File
@@ -1,4 +1,5 @@
import { Config } from "effect"
import { InstallationChannel } from "../installation/version"
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
@@ -10,6 +11,10 @@ function falsy(key: string) {
return value === "false" || value === "0"
}
// Channels that default to the new effect-httpapi server backend. The legacy
// hono backend remains the default for stable (`prod`/`latest`) installs.
const HTTPAPI_DEFAULT_ON_CHANNELS = new Set(["dev", "beta", "local"])
function number(key: string) {
const value = process.env[key]
if (!value) return undefined
@@ -46,7 +51,7 @@ export const Flag = {
KILO_DISABLE_CLAUDE_CODE,
KILO_DISABLE_CLAUDE_CODE_PROMPT: KILO_DISABLE_CLAUDE_CODE || truthy("KILO_DISABLE_CLAUDE_CODE_PROMPT"),
KILO_DISABLE_CLAUDE_CODE_SKILLS,
KILO_DISABLE_EXTERNAL_SKILLS: KILO_DISABLE_CLAUDE_CODE_SKILLS || truthy("KILO_DISABLE_EXTERNAL_SKILLS"),
KILO_DISABLE_EXTERNAL_SKILLS: truthy("KILO_DISABLE_EXTERNAL_SKILLS"), // kilocode_change
KILO_FAKE_VCS: process.env["KILO_FAKE_VCS"],
KILO_SERVER_PASSWORD: process.env["KILO_SERVER_PASSWORD"],
KILO_SERVER_USERNAME: process.env["KILO_SERVER_USERNAME"],
@@ -78,8 +83,16 @@ export const Flag = {
KILO_STRICT_CONFIG_DEPS: truthy("KILO_STRICT_CONFIG_DEPS"),
KILO_WORKSPACE_ID: process.env["KILO_WORKSPACE_ID"],
KILO_EXPERIMENTAL_HTTPAPI: truthy("KILO_EXPERIMENTAL_HTTPAPI"),
// Defaults to true on dev/beta/local channels so internal users exercise the
// new effect-httpapi server backend. Stable (`prod`/`latest`) installs stay
// on the legacy hono backend until the rollout is complete. An explicit env
// var ("true"/"1" or "false"/"0") always wins, providing an opt-in for
// stable users and an escape hatch for dev/beta users.
KILO_EXPERIMENTAL_HTTPAPI:
truthy("KILO_EXPERIMENTAL_HTTPAPI") ||
(!falsy("KILO_EXPERIMENTAL_HTTPAPI") && HTTPAPI_DEFAULT_ON_CHANNELS.has(InstallationChannel)),
KILO_EXPERIMENTAL_WORKSPACES: KILO_EXPERIMENTAL || truthy("KILO_EXPERIMENTAL_WORKSPACES"),
KILO_EXPERIMENTAL_EVENT_SYSTEM: KILO_EXPERIMENTAL || truthy("KILO_EXPERIMENTAL_EVENT_SYSTEM"),
// Evaluated at access time (not module load) because tests, the CLI, and
// external tooling set these env vars at runtime.
+2
View File
@@ -87,6 +87,8 @@ export const layer = Layer.effect(
Effect.sync(() => Service.of(make())),
)
export const defaultLayer = layer
export const layerWith = (input: Partial<Interface>) =>
Layer.effect(
Service,
+2
View File
@@ -1,3 +1,5 @@
export * as Log from "./log"
import path from "path"
import { existsSync, writeFileSync } from "fs" // kilocode_change
import fs from "fs/promises"
@@ -65,6 +65,34 @@ describe("AppFileSystem", () => {
)
})
describe("readFileStringSafe", () => {
it(
"returns file contents when file exists",
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const filesys = yield* FileSystem.FileSystem
const tmp = yield* filesys.makeTempDirectoryScoped()
const file = path.join(tmp, "exists.txt")
yield* filesys.writeFileString(file, "hello")
const result = yield* fs.readFileStringSafe(file)
expect(result).toBe("hello")
}),
)
it(
"returns undefined for missing file (NotFound)",
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const filesys = yield* FileSystem.FileSystem
const tmp = yield* filesys.makeTempDirectoryScoped()
const result = yield* fs.readFileStringSafe(path.join(tmp, "does-not-exist.txt"))
expect(result).toBeUndefined()
}),
)
})
describe("readJson / writeJson", () => {
it(
"round-trips JSON data",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
version = "7.3.1"
version = "7.3.10"
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/v7.3.1/opencode-darwin-arm64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.10/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.1/opencode-darwin-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.10/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.1/opencode-linux-arm64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.10/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/v7.3.1/opencode-linux-x64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.10/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/v7.3.1/opencode-windows-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.10/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+4
View File
@@ -29,6 +29,10 @@ exclude = [
'^https?://ai-gateway\.vercel\.sh/v1/?$',
'^https?://api\.voyageai\.com/v1/embeddings/?$',
'^https?://generativelanguage\.googleapis\.com/v1beta/openai/?$',
# xAI API and OAuth endpoints require request parameters or non-GET methods.
'^https?://api\.x\.ai/v1/?$',
'^https?://auth\.x\.ai/?$',
'^https?://auth\.x\.ai/oauth2/(authorize|device/code|token)/?$',
'https://opencode.ai/pages/config',
'^localhost:3000/',
'https://tbench.ai/',
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-docs",
"version": "7.3.1",
"version": "7.3.10",
"private": true,
"scripts": {
"dev": "next dev --webpack --port 3002",
@@ -33,5 +33,6 @@
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vitest": "^3.2.3"
}
},
"peerDependencies": {}
}
@@ -1,21 +1,23 @@
---
title: Voice Transcription
description: Kilo Code now includes experimental support for voice input in the chat interface.
description: Dictate prompts through your signed-in Kilo account.
---
# Voice Transcription
{% callout type="warning" title="🧪 Experimental Feature" %}
Voice Transcription / speech-to-text (STT) is currently in experimental status. Expect potential issues and changes as the feature matures.
{% callout type="warning" title="Experimental feature" %}
Speech to Text is experimental. Expect issues and changes as it matures.
{% /callout %}
Kilo Code now includes experimental support for voice input in the chat interface. This feature allows you to dictate your messages using speech-to-text (STT) technology powered by OpenAI's Whisper API.
Use voice input in prompt fields instead of typing. Transcription uses your Kilo account through Kilo Gateway.
## Prerequisites
---
Voice transcription requires two components to be set up:
## Get ready
### 1. FFmpeg Installation
Voice input needs FFmpeg plus access to the Kilo provider.
### Install FFmpeg
FFmpeg is required for audio capture and processing. Install it for your platform:
@@ -35,56 +37,72 @@ sudo apt install ffmpeg
**Windows:**
Download from [ffmpeg.org/download.html](https://ffmpeg.org/download.html) and add to your system PATH.
### 2. OpenAI API Key
### Sign in
Voice transcription uses OpenAI's Whisper API for speech recognition. You need an OpenAI API configuration in Kilo Code:
Enable and sign in to the Kilo provider to use voice input in prompt fields. Requests use your Kilo account through Kilo Gateway, so no separate OpenAI provider profile or API key is needed.
1. Configure an OpenAI provider profile in Kilo Code settings
2. Add your OpenAI API key to the profile
3. Either **OpenAI** or **OpenAI Native** provider types will work
---
## Enabling Voice Transcription
## Enable input
Voice transcription is an experimental feature that must be enabled:
Voice input is experimental and must be enabled:
1. Open Kilo Code settings
2. Navigate to **Experimental Features**
2. Open **Experimental** settings
3. Enable the **Speech to Text** experiment
## Using Voice Input
Kilo stores this toggle in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`), not VS Code user settings:
Once configured and enabled, a microphone button will appear in the chat input area:
```json
{
"experimental": {
"speech_to_text": true
}
}
```
---
## Record prompts
Once enabled, a microphone button appears in prompt fields:
1. Click the microphone button to start recording
2. Speak your message clearly
3. Click again to stop recording
4. Your speech will be automatically transcribed into text
4. Your speech is transcribed into text
The feature includes real-time audio level visualization and voice activity detection to automatically detect when you're speaking.
## Technical Details
---
- **Audio Processing**: Uses FFmpeg for system audio capture
- **Voice Recognition**: OpenAI Whisper API for transcription
## Review details
## Troubleshooting
- **Audio processing**: Uses FFmpeg for system audio capture
- **Transcription**: Sends audio through Kilo Gateway with the selected transcription model
---
## Fix issues
**Microphone button not appearing:**
- Ensure the Speech to Text experiment is enabled
- Verify FFmpeg is installed and in your PATH
- Check that you have an OpenAI provider configured with a valid API key
- Enable and sign in to the Kilo provider
**Transcription errors:**
- Verify your OpenAI API key is valid and has available credits
- Confirm the Kilo provider remains enabled and signed in
- Check your internet connection
- Try speaking more clearly or adjusting your microphone settings
## Limitations
---
This feature is currently experimental and may have limitations:
## Know limits
- Requires active internet connection
- Uses OpenAI API credits based on audio duration
Speech to Text is experimental and may have limitations:
- Requires an active internet connection
- Requires Kilo Gateway access through your Kilo account
- Transcription accuracy depends on audio quality and speech clarity
@@ -154,6 +154,7 @@ Options:
--fork fork the session when continuing (use with --continue or --session) [boolean]
--cloud-fork fetch session from cloud and continue locally (use with --session) [boolean]
-p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string]
-u, --username basic auth username (defaults to KILO_SERVER_USERNAME or 'kilo') [string]
```
## kilo run
@@ -165,25 +166,28 @@ Positionals:
message message to send [string] [default: []]
Options:
--help Show help [boolean]
--version Show version number [boolean]
--command the command to run, use message for args [string]
-c, --continue continue the last session [boolean]
-s, --session session id to continue [string]
--fork fork the session before continuing (requires --continue or --session) [boolean]
--share share the session [boolean]
-m, --model model to use in the format of provider/model [string]
--agent agent to use [string]
--format format: default (formatted) or json (raw JSON events) [string] [choices: "default", "json"] [default: "default"]
-f, --file file(s) to attach to message [array]
--title title for the session (uses truncated prompt if no value provided) [string]
--attach attach to a running opencode server (e.g., http://localhost:4096) [string]
-p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string]
--dir directory to run in, path on remote server if attaching [string]
--port port for the local server (defaults to random port if no value provided) [number]
--variant model variant (provider-specific reasoning effort, e.g., high, max, minimal) [string]
--thinking show thinking blocks [boolean] [default: false]
--auto auto-approve all permissions (for autonomous/pipeline usage) [boolean] [default: false]
--help Show help [boolean]
--version Show version number [boolean]
--command the command to run, use message for args [string]
-c, --continue continue the last session [boolean]
-s, --session session id to continue [string]
--fork fork the session before continuing (requires --continue or --session) [boolean]
--cloud-fork fetch session from cloud and continue locally (use with --session) [boolean]
--share share the session [boolean]
-m, --model model to use in the format of provider/model [string]
--agent agent to use [string]
--format format: default (formatted) or json (raw JSON events) [string] [choices: "default", "json"] [default: "default"]
-f, --file file(s) to attach to message [array]
--title title for the session (uses truncated prompt if no value provided) [string]
--attach attach to a running opencode server (e.g., http://localhost:4096) [string]
-p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string]
-u, --username basic auth username (defaults to KILO_SERVER_USERNAME or 'kilo') [string]
--dir directory to run in, path on remote server if attaching [string]
--port port for the local server (defaults to random port if no value provided) [number]
--variant model variant (provider-specific reasoning effort, e.g., high, max, minimal) [string]
--thinking show thinking blocks [boolean] [default: false]
--dangerously-skip-permissions auto-approve permissions that are not explicitly denied (dangerous!) [boolean] [default: false]
--auto auto-approve all permissions (for autonomous/pipeline usage) [boolean] [default: false]
```
## kilo debug
@@ -201,6 +205,7 @@ Commands:
kilo debug snapshot snapshot debugging utilities
kilo debug startup print startup timing
kilo debug agent <name> show agent configuration details
kilo debug info show debug information
kilo debug paths show global paths (data, config, cache, state)
kilo debug wait wait indefinitely (for debugging)
@@ -502,6 +507,16 @@ Options:
--params Tool params as JSON or a JS object literal [string]
```
### kilo debug info
```
show debug information
Options:
--help Show help [boolean]
--version Show version number [boolean]
```
### kilo debug paths
```
@@ -528,7 +543,7 @@ Options:
manage AI providers and credentials
Commands:
kilo auth list list providers [aliases: ls]
kilo auth list list providers and credentials [aliases: ls]
kilo auth login [url] log in to a provider
kilo auth logout log out from a configured provider
@@ -540,7 +555,7 @@ Options:
### kilo auth list
```
list providers
list providers and credentials
Options:
--help Show help [boolean]
@@ -68,7 +68,7 @@ To fill empty seats or remove members ahead of seat deletion, see the [team mana
## Automatic Top-Up
Ensure your team has uninterrupted access to Kilo Code by enabling Automatic Top-Up. This feature keeps your organization's balance funded so you never have to worry about manual recharges.
Ensure your team has uninterrupted access to Kilo Code by enabling [Automatic Top-Up](https://kilo.ai/features/auto-top-ups). This feature keeps your organization's balance funded so you never have to worry about manual recharges. Auto top-up is also available for individual accounts — see [Adding Credits](/docs/getting-started/adding-credits) for details.
### How It Works
@@ -16,8 +16,9 @@ This document will help you set up your development environment and understand h
Before you begin, make sure you have the following installed:
1. **Git** - For version control
2. **Node.js** (version v20.18.1 (See `.nvmrc` for latest) or higher recommended) and npm
2. **Bun 1.3.13+** - Required for installing dependencies and running scripts
3. **Visual Studio Code** - Our recommended IDE for development
4. **Java 21** - Required only when running JetBrains plugin checks or repo-level checks that include `@kilocode/kilo-jetbrains`
## Getting Started
@@ -37,10 +38,10 @@ Before you begin, make sure you have the following installed:
1. **Install dependencies**:
```bash
pnpm install
bun install
```
This command will install dependencies for the main extension, webview UI, and e2e tests.
This command will install dependencies for all workspace packages.
1. **Install VSCode Extensions**:
- **Required**: [ESBuild Problem Matchers](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) - Helps display build errors correctly.
@@ -54,134 +55,154 @@ The full list of recommended extensions is in `.vscode/extensions.json`
### Project Structure
The project is organized into several key directories:
The project is organized into several key packages:
- **`src/`** - Core extension code
- **`core/`** - Core functionality and tools
- **`services/`** - Service implementations
- **`webview-ui/`** - Frontend UI code
- **`e2e/`** - End-to-end tests
- **`scripts/`** - Utility scripts
- **`assets/`** - Static assets like images and icons
- **`packages/opencode/`** - Kilo CLI, agent runtime, local HTTP server, session management, and TUI
- **`packages/kilo-vscode/`** - VS Code extension, webview UI, Agent Manager, and extension packaging
- **`packages/sdk/js/`** - Generated TypeScript SDK for the local server API
- **`packages/kilo-docs/`** - Documentation site
- **`packages/kilo-jetbrains/`** - JetBrains plugin
## Development Workflow
### Building the Extension
### Running the CLI
To build the extension:
To run the CLI from the repo root:
```bash
pnpm build
bun dev
```
This will:
`bun dev` and `bun run dev` are equivalent. Both run the local source in `packages/opencode/`; they do not use a globally installed `kilo` binary.
1. Build the webview UI
2. Compile TypeScript
3. Bundle the extension
4. Create a `.vsix` file in the `bin/` directory
### Backend/API Validation
For backend and API validation, use the root [TESTING.md](https://github.com/Kilo-Org/kilocode/blob/main/TESTING.md) guide. It covers starting the local backend with:
```bash
bun dev serve
```
and validating behavior with `curl` requests against the local server.
If you change server endpoints in `packages/opencode/src/server/`, regenerate the SDK from the repo root:
```bash
./script/generate.ts
```
### Running the Extension
To run the extension in development mode:
1. Press `F5` (or select **Run** → **Start Debugging**) in VSCode
2. This will open a new VSCode window with Kilo Code loaded
### Hot Reloading
- **Webview UI changes**: Changes to the webview UI will appear immediately without restarting
- **Core extension changes**: Changes to the core extension code will automatically reload the ext host
In development mode (NODE_ENV="development"), changing the core code will trigger a `workbench.action.reloadWindow` command, so it is no longer necessary to manually start/stop the debugger and tasks.
> **Important**: In production builds, when making changes to the core extension, you need to:
>
> 1. Stop the debugging process
> 2. Kill any npm tasks running in the background (see screenshot below)
> 3. Start debugging again
{% image src="https://github.com/user-attachments/assets/466fb76e-664d-4066-a3f2-0df4d57dd9a4" alt="Stopping background tasks" width="600" /%}
### Installing the Built Extension
To install your built extension:
```bash
code --install-extension "$(ls -1v bin/kilo-code-*.vsix | tail -n1)"
bun run extension
```
Replace `[version]` with the current version number.
This will build and launch the extension in an isolated VS Code instance.
### Building the Extension
From `packages/kilo-vscode/`:
```bash
bun run compile
bun run package
```
Use `bun run compile` when you need a development build and `bun run package` when you need a production extension bundle.
## Testing
Kilo Code uses several types of tests to ensure quality:
### Unit Tests
### Repo-Level Checks
Run unit tests with:
From the repo root:
```bash
npm test
bun install
bun run lint
bun run typecheck
```
This runs both extension and webview tests.
`bun run typecheck` wraps `bun turbo typecheck`. Use `bun turbo typecheck --force` if you need to bypass the Turbo cache.
To run specific test suites:
Do **not** run `bun test` from the repo root. The root test script intentionally exits with failure to prevent accidentally running tests from the wrong package.
### CLI Checks
From `packages/opencode/`:
```bash
npm run test:extension # Run only extension tests
npm run test:webview # Run only webview tests
bun run typecheck
bun test
bun test ./path/to/file.test.ts
```
### End-to-End Tests
Use the root [TESTING.md](https://github.com/Kilo-Org/kilocode/blob/main/TESTING.md) guide for backend/API checks that require `bun dev serve` and `curl`-based requests.
E2E tests verify the extension works correctly within VSCode:
### VS Code Extension Checks
1. Create a `.env.local` file in the root with required API keys:
```
OPENROUTER_API_KEY=sk-or-v1-...
```
2. Run the integration tests:
```bash
npm run test:integration
```
For more details on E2E tests, see e2e/VSCODE_INTEGRATION_TESTS
## Linting and Type Checking
Ensure your code meets our quality standards:
From `packages/kilo-vscode/`:
```bash
npm run lint # Run ESLint
npm run check-types # Run TypeScript type checking
bun run typecheck
bun run lint
bun run test:unit
bun run test
bun run compile
bun run package
```
### Documentation Checks
From the repo root:
```bash
bun run --filter @kilocode/kilo-docs test
bun run --filter @kilocode/kilo-docs build
bun run --filter @kilocode/kilo-docs dev
```
For manual documentation validation, run the docs site locally, preview the affected page, and check the changed links and rendered content.
### Testing Evidence for Pull Requests
Every PR marked ready for review must include testing evidence. A bare `Not tested` or `N/A` answer is not sufficient.
Choose checks that match the files touched. Docs-only, config-only, and similar changes may satisfy this rule with concrete manual verification or a relevant command check.
For CLI and extension changes, useful evidence can include:
- The relevant command checks from the package you changed
- Manual/local verification of the changed behavior in the CLI or extension
- Screenshots or videos for visual changes, such as a settings page update or changed CLI/extension behavior
For docs changes, useful evidence can include:
- `bun run script/check-md-table-padding.ts --fix`
- `bun run --filter @kilocode/kilo-docs test`
- Previewing the changed docs page locally, as described in [Documentation Contributions](/docs/contributing#documentation-contributions)
If you cannot complete a relevant command, include all of the following in the PR:
- The command you attempted or would normally run
- The blocker or failure that prevented completion
- The substitute verification you performed instead
Agent limitations, local resource constraints, OOM constraints, or an agent prompt that says to skip tests do not waive this requirement. Draft PRs may be incomplete until they are marked ready for review. Maintainers may still defer or close review at their discretion.
## Guardrails
- User-facing changes usually need a changeset. Run `bunx changeset add` or add a file under `.changeset/`.
- After changing server endpoints, run `./script/generate.ts` from the repo root to regenerate `packages/sdk/js/`.
- After adding or changing guarded URLs in `packages/kilo-vscode/`, `packages/kilo-vscode/webview-ui/`, or `packages/opencode/src/`, run `bun run script/extract-source-links.ts` from the repo root.
- When editing shared `packages/opencode/` files, keep changes small and mark Kilo-only edits with `// kilocode_change` for a single line or `// kilocode_change start` / `// kilocode_change end` for a block. Do not add these markers inside `kilocode`-named paths.
## Git Hooks
This project uses [Husky](https://typicode.github.io/husky/) to manage Git hooks, which automate certain checks before commits and pushes. The hooks are located in the `.husky/` directory.
### Pre-commit Hook
Before a commit is finalized, the `.husky/pre-commit` hook runs:
1. **Branch Check**: Prevents committing directly to the `main` branch.
2. **Type Generation**: Runs `npm run generate-types`.
3. **Type File Check**: Ensures that any changes made to `src/exports/roo-code.d.ts` by the type generation are staged.
4. **Linting**: Runs `lint-staged` to lint and format staged files.
### Pre-push Hook
Before changes are pushed to the remote repository, the `.husky/pre-push` hook runs:
1. **Branch Check**: Prevents pushing directly to the `main` branch.
2. **Compilation**: Runs `npm run compile` to ensure the project builds successfully.
3. **Changeset Check**: Checks if a changeset file exists in `.changeset/` and reminds you to create one using `npm run changeset` if necessary.
These hooks help maintain code quality and consistency. If you encounter issues with commits or pushes, check the output from these hooks for error messages.
This project uses [Husky](https://typicode.github.io/husky/) to manage Git hooks. The current pre-push hook checks the Bun version against root `package.json` and runs the repo-level typecheck.
## Troubleshooting
@@ -189,7 +210,8 @@ These hooks help maintain code quality and consistency. If you encounter issues
1. **Extension not loading**: Check the VSCode Developer Tools (Help > Toggle Developer Tools) for errors
2. **Webview not updating**: Try reloading the window (Developer: Reload Window)
3. **Build errors**: Make sure all dependencies are installed with `npm run install:all`
3. **Build errors**: Make sure all dependencies are installed with `bun install`
4. **Root tests fail immediately**: This is expected. Run package-level tests instead of root `bun test`
### Debugging Tips
+26 -10
View File
@@ -85,13 +85,25 @@ Guidelines:
Skip the changeset only for internal refactors, CI tweaks, test-only changes, or docs that do not affect users.
### Other Guardrails
- Regenerate `packages/sdk/js/` with `./script/generate.ts` after changing server endpoints.
- Run `bun run script/extract-source-links.ts` after adding or changing guarded URLs in `packages/kilo-vscode/`, `packages/kilo-vscode/webview-ui/`, or `packages/opencode/src/`.
- When editing shared `packages/opencode/` files, keep Kilo changes small and mark Kilo-only edits with `// kilocode_change` for a single line or `// kilocode_change start` / `// kilocode_change end` for a block. Do not add these markers inside `kilocode`-named paths.
### Testing Your Changes
- Run the test suite:
```bash
npm test
```
- Manually test your changes in the development extension
Use the current Bun commands in the [Development Environment](/docs/contributing/development-environment) guide for repo-level, CLI, backend/API, VS Code extension, and docs checks.
Key reminders:
- Do **not** run `bun test` from the repo root. The root test script intentionally exits with failure so tests run from the package that owns them.
- Use the root [TESTING.md](https://github.com/Kilo-Org/kilocode/blob/main/TESTING.md) guide for backend/API validation with `bun dev serve` and `curl`-based requests.
- Regenerate the SDK with `./script/generate.ts` from the repo root after changing server endpoints.
- Manually verify extension behavior with `bun run extension`.
- For manual documentation validation, preview the affected page and check changed links and rendered content.
Before marking a PR ready for review, include testing evidence in the PR template. See [Testing Evidence for Pull Requests](/docs/contributing/development-environment#testing-evidence-for-pull-requests) for the full standard, including docs/config-only verification and blocked command fallback requirements.
### Creating a Pull Request
@@ -110,7 +122,9 @@ Skip the changeset only for internal refactors, CI tweaks, test-only changes, or
5. Fill out the PR template with:
- A clear description of the changes
- Any related issues
- Testing steps
- Testing evidence, including commands run and results
- Manual/local verification performed
- Any command blocker plus substitute verification
- Screenshots (if applicable)
## Contributing to the Kilo Marketplace
@@ -143,14 +157,16 @@ Documentation improvements are highly valued contributions:
- Use absolute paths starting from `/docs/` for internal links (except within the same directory)
- Don't include `.md` extensions in links
2. Test your documentation changes by running the docs site locally:
2. Test your documentation changes and run the docs site locally from the repo root:
```bash
cd packages/kilo-docs
pnpm install
pnpm dev
bun run --filter @kilocode/kilo-docs test
bun run --filter @kilocode/kilo-docs build
bun run --filter @kilocode/kilo-docs dev
```
For manual validation, preview the affected page and check changed links and rendered content.
3. Submit a PR with your documentation changes
## Community Guidelines
+3 -3
View File
@@ -80,10 +80,10 @@ your-project/
### Compatibility Directories
For interoperability with other tools, the CLI also loads skills from:
For interoperability with other tools, Kilo Code also loads skills from:
- `.claude/skills/` — Claude Code compatibility
- `.agents/skills/` — Open agent standard
- `.agents/skills/` — Open agent standard, loaded by default
- `.claude/skills/` — Claude Code compatibility, loaded when Claude Code Compatibility is enabled
### Additional Skill Paths and Remote URLs
@@ -9,9 +9,9 @@ Once you've used any initial free Kilo Credits, you can easily add more:
- Subscribe to the [Kilo Pass](https://kilo.ai/features/kilo-pass), the most cost effective way to add credits.
- Purchase additional credits as a one-time transaction.
- Enable automatic top-up, which purchases additional credits when your balance is below $5.
- Enable [automatic top-up](https://kilo.ai/features/auto-top-ups), which purchases additional credits when your balance runs low. Auto top-up is available for both individual and organization accounts.
These options are available to purchase from your [personal profile page](https://app.kilo.ai/profile).
These options are available to purchase from your [personal profile page](https://app.kilo.ai/profile). Organization owners can manage credits and auto top-up from the [Organization dashboard](https://app.kilo.ai).
You can also use subscriptions or credits you may have purchased directly with an AI provider by adding your keys on the [Bring your own Key (BYOK)](https://app.kilo.ai/byok) settings screen. For setup details and supported providers, see [AI Providers documentation](/docs/ai-providers). If your provider is not yet supported, you can also [directly connect your provider](/docs/getting-started/setup-authentication) in the extension and CLI.
@@ -198,10 +198,21 @@ Available experimental toggles include:
- **Share mode** — `manual`, `auto`, or `disabled` session sharing
- **LSP integration** — expose language server diagnostics to the agent
- **Paste summary** — summarize large clipboard pastes before including them
- **Speech to Text**: enable voice transcription in chat
- **Batch tool** — allow the agent to batch multiple tool calls in one step
- **Agent Manager Tool** - allow agents to start Agent Manager local and worktree sessions from chat
- **OpenTelemetry** — enable Kilo telemetry and optional OTLP export when configured
Speech to Text is enabled from this Experimental tab. Kilo stores that toggle in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`), not VS Code user settings:
```json
{
"experimental": {
"speech_to_text": true
}
}
```
Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`:
```json
@@ -871,4 +871,10 @@ module.exports = [
basePath: false,
permanent: true,
},
{
source: "/auto-top-ups",
destination: "https://kilo.ai/features/auto-top-ups",
basePath: false,
permanent: true,
},
]
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18d394d726ebee312e54db5d45f55cca3a1728d2b41f9f73bb9b610096bf53e0
size 41004
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7aa0924c6679cbff6e816dd86371a2d483afb7f0840824503ef4ba325c25d6c5
size 26282
oid sha256:97728b91ed1c92556abfde08824b91536e87cc7afa4d121bb5b1b488946c1d05
size 26009
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f28a551185ec9d7607d02295803b155e0f7bac378fcefdf0996c6ca51c0afad6
size 21892
oid sha256:7b8e9f6e0b9a9a62efc9b1e807936049c7627674ba241239dfd777698f951acc
size 21820
+18 -1
View File
@@ -1,13 +1,20 @@
# Source Code Links
<!-- Auto-generated by script/extract-source-links.ts — DO NOT EDIT -->
<!-- 86 unique URLs extracted from extension and CLI source -->
- <https://accounts.x.ai>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://api.apertis.ai/v1>
<!-- packages/opencode/src/provider/model-cache.ts -->
<!-- packages/opencode/src/provider/models.ts -->
- <https://api.inceptionlabs.ai/v1/fim/completions>
<!-- packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts -->
- <https://api.kilo.ai>
<!-- packages/opencode/src/cli/cmd/github.ts -->
- <https://api.mistral.ai/v1/fim/completions>
<!-- packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts -->
- <https://api.x.ai/v1>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://app.kilo.ai>
<!-- packages/opencode/src/kilocode/kilo-commands.tsx -->
- <https://app.kilo.ai/claw>
@@ -24,6 +31,14 @@
<!-- packages/kilo-vscode/webview-ui/src/components/profile/ProfileView.tsx -->
- <https://app.kilo.ai/usage>
<!-- packages/opencode/src/kilocode/components/dialog-kilo-profile.tsx -->
- <https://auth.x.ai>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://auth.x.ai/oauth2/authorize>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://auth.x.ai/oauth2/device/code>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://auth.x.ai/oauth2/token>
<!-- packages/opencode/src/plugin/xai.ts -->
- <https://blog.kilo.ai/p/new-kilo-for-vs-code-is-live>
<!-- packages/kilo-vscode/webview-ui/src/components/migration/MigrationWizard.tsx -->
- <https://chatgpt.com/explore/plus>
@@ -32,6 +47,8 @@
<!-- packages/kilo-vscode/src/agent-manager/WorktreeManager.ts -->
- <https://cloudflare.com/cdn-cgi/trace>
<!-- packages/opencode/src/session/network.ts -->
- <https://codestral.mistral.ai/v1/fim/completions>
<!-- packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts -->
- <https://cookbook.openai.com/examples/using_logprobs>
<!-- packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts -->
- <https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services>
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-gateway",
"version": "7.3.1",
"version": "7.3.10",
"type": "module",
"license": "MIT",
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
+15
View File
@@ -57,11 +57,24 @@ export {
type OrganizationModeConfig,
} from "./api/modes.js"
export { fetchKilocodeNotifications, type KilocodeNotification } from "./api/notifications.js"
export { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "./cloud-sessions.js"
// ============================================================================
// Server Routes (optional - requires hono and OpenCode dependencies)
// ============================================================================
export { createKiloRoutes } from "./server/routes.js"
export {
GatewayError,
UnauthorizedError,
getOrganizationId,
getClawChatCredentials,
getClawStatus,
getCloudSessions,
getNotifications,
getProfile,
getToken,
setOrganization,
} from "./server/handlers.js"
// ============================================================================
// Note: TUI exports moved to separate entry point
@@ -97,6 +110,8 @@ export {
ENV_KILO_API_URL,
DEFAULT_KILO_API_URL,
KILO_API_BASE,
KILO_CHAT_URL,
KILO_EVENT_SERVICE_URL,
KILO_OPENROUTER_BASE,
POLL_INTERVAL_MS,
DEFAULT_MODEL,
@@ -0,0 +1,190 @@
import { fetchBalance, fetchProfile } from "../api/profile.js"
import { fetchKilocodeNotifications } from "../api/notifications.js"
import { clearModesCache } from "../api/modes.js"
import { HEADER_ORGANIZATIONID, KILO_API_BASE, KILO_CHAT_URL, KILO_EVENT_SERVICE_URL } from "../api/constants.js"
import type { KilocodeBalance, KilocodeProfile } from "../types.js"
import { buildKiloHeaders } from "../headers.js"
export type KiloAuth =
| { type: "api"; key: string }
| { type: "oauth"; access: string; refresh: string; expires: number; accountId?: string }
| { type: "wellknown"; key: string; token: string }
export interface KiloProfileResult {
profile: KilocodeProfile
balance: KilocodeBalance | null
currentOrgId: string | null
}
export interface ClawChatCredentials {
token: string
expiresAt: string
kiloChatUrl: string
eventServiceUrl: string
}
export interface AuthStore {
get(provider: string): Promise<KiloAuth | undefined>
set(provider: string, auth: Extract<KiloAuth, { type: "oauth" }>): Promise<void>
}
export interface OrganizationDeps {
auth: AuthStore
clear(): void
dispose(): Promise<void>
}
export interface CloudSessionsInput {
cursor?: string
limit?: number
gitUrl?: string
}
export class UnauthorizedError extends Error {}
export class GatewayError extends Error {
constructor(
message: string,
readonly status: number,
) {
super(message)
}
}
export function getToken(auth: KiloAuth | undefined) {
if (auth?.type === "api") return auth.key
if (auth?.type === "oauth") return auth.access
return undefined
}
export function getOrganizationId(auth: KiloAuth | undefined) {
if (auth?.type === "oauth") return auth.accountId
return undefined
}
export async function getProfile(auth: AuthStore): Promise<KiloProfileResult> {
const info = await auth.get("kilo")
if (!info || info.type !== "oauth") throw new UnauthorizedError("Not authenticated with Kilo Gateway")
const currentOrgId = info.accountId ?? null
const [profile, balance] = await Promise.all([
fetchProfile(info.access),
fetchBalance(info.access, currentOrgId ?? undefined),
])
return { profile, balance, currentOrgId }
}
export async function getNotifications(auth: AuthStore) {
const info = await auth.get("kilo")
const token = getToken(info)
if (!token) return []
return fetchKilocodeNotifications({
kilocodeToken: token,
kilocodeOrganizationId: getOrganizationId(info),
})
}
export async function setOrganization(deps: OrganizationDeps, organizationId: string | null) {
const info = await deps.auth.get("kilo")
if (!info || info.type !== "oauth") throw new UnauthorizedError("Not authenticated with Kilo Gateway")
await deps.auth.set("kilo", {
type: "oauth",
refresh: info.refresh,
access: info.access,
expires: info.expires,
...(organizationId && { accountId: organizationId }),
})
deps.clear()
clearModesCache()
await deps.dispose()
return true
}
export async function getClawStatus(auth: AuthStore) {
const info = await auth.get("kilo")
const token = getToken(info)
if (!token) throw new UnauthorizedError("No valid token found")
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
const org = getOrganizationId(info)
if (org) headers[HEADER_ORGANIZATIONID] = org
const response = await fetch(`${KILO_API_BASE}/api/kiloclaw/status`, { headers })
if (!response.ok) throw new GatewayError(await response.text(), response.status)
return response.json()
}
export async function getClawChatCredentials(auth: AuthStore): Promise<ClawChatCredentials> {
const info = await auth.get("kilo")
const token = getToken(info)
if (!token) throw new UnauthorizedError("No valid token found")
const expires = info?.type === "oauth" ? info.expires : Date.now() + 365 * 24 * 60 * 60 * 1000
return {
token,
expiresAt: new Date(expires).toISOString(),
kiloChatUrl: KILO_CHAT_URL,
eventServiceUrl: KILO_EVENT_SERVICE_URL,
}
}
export async function getCloudSessions(token: string, input: CloudSessionsInput) {
const query: Record<string, unknown> = {}
if (input.cursor) query.cursor = input.cursor
if (input.limit) query.limit = input.limit
if (input.gitUrl) query.gitUrl = input.gitUrl
const params = new URLSearchParams({
batch: "1",
input: JSON.stringify({ "0": query }),
})
const response = await fetch(`${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${params.toString()}`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
...buildKiloHeaders(),
},
})
if (!response.ok) {
const text = await response.text()
console.error("[Kilo Gateway] cloud-sessions: tRPC request failed", {
status: response.status,
body: text.slice(0, 500),
})
throw new GatewayError(`Cloud sessions fetch failed: ${response.status}`, response.status)
}
const raw = await response.text()
const json = JSON.parse(raw)
const data = Array.isArray(json) ? json[0]?.result?.data : null
const result = data?.json ?? data
if (!result) return { cliSessions: [], nextCursor: null }
const cliSessions = (result.cliSessions ?? []).map((item: any) => ({
session_id: item.session_id,
title: item.title ?? null,
created_at:
typeof item.created_at === "string"
? item.created_at
: item.created_at
? new Date(item.created_at).toISOString()
: new Date().toISOString(),
updated_at:
typeof item.updated_at === "string"
? item.updated_at
: item.updated_at
? new Date(item.updated_at).toISOString()
: new Date().toISOString(),
version: item.version ?? 0,
}))
return { cliSessions, nextCursor: result.nextCursor ?? null }
}
+43 -155
View File
@@ -5,20 +5,23 @@
* This factory function accepts OpenCode dependencies to create Kilo-specific routes
*/
import { fetchProfile, fetchBalance } from "../api/profile.js"
import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js"
import { fetchOrganizationModes, clearModesCache } from "../api/modes.js"
import {
KILO_API_BASE,
KILO_CHAT_URL,
KILO_EVENT_SERVICE_URL,
HEADER_FEATURE,
HEADER_ORGANIZATIONID,
} from "../api/constants.js"
import { KILO_API_BASE, HEADER_FEATURE, HEADER_ORGANIZATIONID } from "../api/constants.js"
import { buildKiloHeaders } from "../headers.js"
import type { ImportDeps, DrizzleDb } from "../cloud-sessions.js"
import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js"
import { createFimHandler } from "./fim.js"
import {
GatewayError,
UnauthorizedError,
getClawChatCredentials,
getClawStatus,
getCloudSessions,
getNotifications,
getProfile,
setOrganization,
} from "./handlers.js"
// Type definitions for OpenCode dependencies (injected at runtime)
type Hono = any
@@ -39,7 +42,7 @@ interface KiloRoutesDeps extends ImportDeps {
Auth: Auth
ModelCache: ModelCache
z: Z
InstanceStore: { disposeAllInstances(): Promise<void> }
Instances: { disposeAllInstances(): Promise<void> }
}
/**
@@ -84,7 +87,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
SessionCreatedEvent,
Identifier,
ModelCache,
InstanceStore,
Instances,
} = deps
const Organization = z.object({
@@ -166,24 +169,12 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
},
}),
async (c: any) => {
// Get Kilo auth
const auth = await Auth.get("kilo")
if (!auth || auth.type !== "oauth") {
try {
return c.json(await getProfile(Auth))
} catch (err) {
if (!(err instanceof UnauthorizedError)) throw err
return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
}
const token = auth.access
const currentOrgId = auth.accountId ?? null
// Fetch profile and balance in parallel
// Pass organizationId to fetchBalance to get team balance when in org context
const [profile, balance] = await Promise.all([
fetchProfile(token),
fetchBalance(token, currentOrgId ?? undefined),
])
return c.json({ profile, balance, currentOrgId })
},
)
.post(
@@ -213,27 +204,21 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
async (c: any) => {
const { organizationId } = c.req.valid("json")
// Get current Kilo auth
const auth = await Auth.get("kilo")
if (!auth || auth.type !== "oauth") {
try {
return c.json(
await setOrganization(
{
auth: Auth,
clear: () => ModelCache.clear("kilo"),
dispose: () => Instances.disposeAllInstances(),
},
organizationId,
),
)
} catch (err) {
if (!(err instanceof UnauthorizedError)) throw err
return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
}
// Update auth with new organization ID
await Auth.set("kilo", {
type: "oauth",
refresh: auth.refresh,
access: auth.access,
expires: auth.expires,
...(organizationId && { accountId: organizationId }),
})
ModelCache.clear("kilo")
clearModesCache()
await InstanceStore.disposeAllInstances()
return c.json(true)
},
)
.get(
@@ -420,19 +405,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
},
}),
async (c: any) => {
const auth = await Auth.get("kilo")
if (!auth) return c.json([])
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json([])
const organizationId = auth.type === "oauth" ? auth.accountId : undefined
const notifications = await fetchKilocodeNotifications({
kilocodeToken: token,
kilocodeOrganizationId: organizationId,
})
return c.json(notifications)
return c.json(await getNotifications(Auth))
},
)
.get(
@@ -581,29 +554,11 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
}),
async (c: any) => {
try {
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)
const organizationId = auth.type === "oauth" ? auth.accountId : undefined
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
if (organizationId) {
headers[HEADER_ORGANIZATIONID] = organizationId
}
const response = await fetch(`${KILO_API_BASE}/api/kiloclaw/status`, { headers })
if (!response.ok) {
const text = await response.text()
return c.json({ error: `KiloClaw request failed: ${response.status} ${text}` }, response.status as any)
}
return c.json(await response.json())
return c.json(await getClawStatus(Auth))
} catch (err: any) {
if (err instanceof GatewayError) {
return c.json({ error: `KiloClaw request failed: ${err.status} ${err.message}` }, err.status as any)
}
console.error("[Kilo Gateway] claw/status: error", err?.message ?? err)
return c.json({ error: "Failed to reach KiloClaw" }, 502)
}
@@ -640,25 +595,12 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
},
}),
async (c: any) => {
const auth = await Auth.get("kilo")
if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)
// For OAuth, expires is a millisecond epoch we already track. For
// API tokens we don't have a verified expiry locally — the JWT is
// signed by the cloud and validated by kilo-chat/event-service on
// every request. Use a far-future placeholder so the client cache
// doesn't refetch unnecessarily; on 401 the client clears the
// cache and prompts re-auth.
const expiresAtMs = auth.type === "oauth" ? auth.expires : Date.now() + 365 * 24 * 60 * 60 * 1000
return c.json({
token,
expiresAt: new Date(expiresAtMs).toISOString(),
kiloChatUrl: KILO_CHAT_URL,
eventServiceUrl: KILO_EVENT_SERVICE_URL,
})
try {
return c.json(await getClawChatCredentials(Auth))
} catch (err) {
if (!(err instanceof UnauthorizedError)) throw err
return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
}
},
)
.get(
@@ -708,63 +650,9 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
if (!token) return c.json({ error: "No valid token found" }, 401)
const { cursor, limit, gitUrl } = c.req.valid("query")
const input: Record<string, unknown> = {}
if (cursor) input.cursor = cursor
if (limit) input.limit = limit
if (gitUrl) input.gitUrl = gitUrl
const params = new URLSearchParams({
batch: "1",
input: JSON.stringify({ "0": input }),
})
const url = `${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${params.toString()}`
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
...buildKiloHeaders(),
},
})
if (!response.ok) {
const text = await response.text()
console.error("[Kilo Gateway] cloud-sessions: tRPC request failed", {
status: response.status,
body: text.slice(0, 500),
})
return c.json({ error: `Cloud sessions fetch failed: ${response.status}` }, response.status as any)
}
const raw = await response.text()
const json = JSON.parse(raw)
const data = Array.isArray(json) ? json[0]?.result?.data : null
const result = data?.json ?? data
if (!result) return c.json({ cliSessions: [], nextCursor: null })
const sessions = (result.cliSessions ?? []).map((s: any) => ({
session_id: s.session_id,
title: s.title ?? null,
created_at:
typeof s.created_at === "string"
? s.created_at
: s.created_at
? new Date(s.created_at).toISOString()
: new Date().toISOString(),
updated_at:
typeof s.updated_at === "string"
? s.updated_at
: s.updated_at
? new Date(s.updated_at).toISOString()
: new Date().toISOString(),
version: s.version ?? 0,
}))
return c.json({ cliSessions: sessions, nextCursor: result.nextCursor ?? null })
return c.json(await getCloudSessions(token, c.req.valid("query")))
} catch (err: any) {
if (err instanceof GatewayError) return c.json({ error: err.message }, err.status as any)
console.error("[Kilo Gateway] cloud-sessions: unhandled error", err?.message ?? err)
return c.json({ error: "Internal error" }, 500)
}
+4 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-i18n",
"version": "7.3.1",
"version": "7.3.10",
"type": "module",
"license": "MIT",
"description": "Kilo-specific i18n translations and overrides",
@@ -16,5 +16,7 @@
"@types/bun": "catalog:",
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
},
"dependencies": {},
"peerDependencies": {}
}
+4 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-indexing",
"version": "7.3.1",
"version": "7.3.10",
"type": "module",
"license": "MIT",
"description": "Standalone indexing engine and host helpers for Kilo Code",
@@ -40,6 +40,7 @@
"@qdrant/js-client-rest": "1.17.0",
"async-mutex": "0.5.0",
"chokidar": "4.0.3",
"effect": "catalog:",
"glob": "13.0.6",
"hono": "catalog:",
"hono-openapi": "catalog:",
@@ -57,5 +58,6 @@
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:"
}
},
"peerDependencies": {}
}
+103 -1
View File
@@ -1,3 +1,4 @@
import { Schema } from "effect"
import z from "zod"
import type { IndexingConfigInput } from "./indexing/config-manager"
import type { EmbedderProvider } from "./indexing/interfaces/manager"
@@ -14,6 +15,7 @@ const providers = [
"openrouter",
"voyage",
] as const satisfies readonly EmbedderProvider[]
const stores = ["lancedb", "qdrant"] as const
export const IndexingConfig = z
.object({
@@ -26,7 +28,7 @@ export const IndexingConfig = z
.positive()
.optional()
.describe("Override embedding vector dimension (auto-detected from model if omitted)"),
vectorStore: z.enum(["lancedb", "qdrant"]).optional().describe("Vector store backend (default: qdrant)"),
vectorStore: z.enum(stores).optional().describe("Vector store backend (default: qdrant)"),
kilo: z
.object({
apiKey: z.string().optional(),
@@ -128,6 +130,106 @@ export const IndexingConfig = z
export type IndexingConfig = z.infer<typeof IndexingConfig>
const Provider = Schema.Literals(providers)
const Store = Schema.Literals(stores)
const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0))
const Score = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1))
export const IndexingSchema = Schema.Struct({
enabled: Schema.optional(Schema.Boolean).annotate({ description: "Enable codebase indexing" }),
provider: Schema.optional(Provider).annotate({
description: "Embedding provider to use for codebase indexing",
}),
model: Schema.optional(Schema.String).annotate({
description: "Embedding model ID (uses provider default if omitted)",
}),
dimension: Schema.optional(PositiveInt).annotate({
description: "Override embedding vector dimension (auto-detected from model if omitted)",
}),
vectorStore: Schema.optional(Store).annotate({ description: "Vector store backend (default: qdrant)" }),
kilo: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
baseUrl: Schema.optional(Schema.String),
organizationId: Schema.optional(Schema.String),
}),
).annotate({ description: "Kilo-hosted embedding provider options" }),
openai: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "OpenAI embedding provider options" }),
ollama: Schema.optional(
Schema.Struct({
baseUrl: Schema.optional(Schema.String),
}),
).annotate({ description: "Ollama embedding provider options" }),
"openai-compatible": Schema.optional(
Schema.Struct({
baseUrl: Schema.optional(Schema.String),
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "OpenAI-compatible embedding provider options" }),
gemini: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "Gemini embedding provider options" }),
mistral: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "Mistral embedding provider options" }),
"vercel-ai-gateway": Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "Vercel AI Gateway embedding provider options" }),
bedrock: Schema.optional(
Schema.Struct({
region: Schema.optional(Schema.String),
profile: Schema.optional(Schema.String),
}),
).annotate({ description: "AWS Bedrock embedding provider options" }),
openrouter: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
specificProvider: Schema.optional(Schema.String),
}),
).annotate({ description: "OpenRouter embedding provider options" }),
voyage: Schema.optional(
Schema.Struct({
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "Voyage embedding provider options" }),
qdrant: Schema.optional(
Schema.Struct({
url: Schema.optional(Schema.String),
apiKey: Schema.optional(Schema.String),
}),
).annotate({ description: "Qdrant vector store connection options" }),
lancedb: Schema.optional(
Schema.Struct({
directory: Schema.optional(Schema.String),
}),
).annotate({ description: "LanceDB vector store options" }),
searchMinScore: Schema.optional(Score).annotate({
description: "Minimum similarity score for search results (default: 0.4)",
}),
searchMaxResults: Schema.optional(PositiveInt).annotate({
description: "Maximum number of search results (default: 50)",
}),
embeddingBatchSize: Schema.optional(PositiveInt).annotate({
description: "Number of code segments per embedding batch (default: 60)",
}),
scannerMaxBatchRetries: Schema.optional(PositiveInt).annotate({
description: "Maximum retry attempts for failed embedding batches (default: 3)",
}),
}).annotate({
identifier: "IndexingConfig",
description: "Codebase indexing configuration",
})
export function toIndexingConfigInput(cfg: IndexingConfig | undefined): IndexingConfigInput {
const provider = cfg?.provider ?? "openai"
@@ -302,10 +302,9 @@ export class CodeIndexConfigManager {
}
public get currentModelDimension(): number | undefined {
if (this.modelDimension && this.modelDimension > 0) return this.modelDimension
const id = this.modelId ?? getDefaultModelId(this.embedderProvider)
const dim = getModelDimension(this.embedderProvider, id)
if (!dim && this.modelDimension && this.modelDimension > 0) return this.modelDimension
return dim
return getModelDimension(this.embedderProvider, id)
}
public get currentSearchMinScore(): number {
@@ -25,7 +25,8 @@ interface EmbeddingItem {
}
interface OpenRouterEmbeddingResponse {
data: EmbeddingItem[]
data?: EmbeddingItem[]
error?: string | { code?: string | number; message?: string }
usage?: {
prompt_tokens?: number
total_tokens?: number
@@ -193,10 +194,7 @@ export class OpenRouterEmbedder implements IEmbedder {
const requestParams: any = {
input: batchTexts,
model: model,
// OpenAI package (as of v4.78.1) has a parsing issue that truncates embedding dimensions to 256
// when processing numeric arrays, which breaks compatibility with models using larger dimensions.
// By requesting base64 encoding, we bypass the package's parser and handle decoding ourselves.
encoding_format: "base64",
encoding_format: "float",
}
if (this.dimensions !== undefined) {
@@ -213,8 +211,24 @@ export class OpenRouterEmbedder implements IEmbedder {
}
const response = (await this.embeddingsClient.embeddings.create(requestParams)) as OpenRouterEmbeddingResponse
const err = response.error
const msg = typeof err === "string" ? err : err?.message
const code = typeof err === "object" && err ? err.code : undefined
if (!response.data || response.data.length === 0) {
log.warn("OpenRouter embedder batch returned invalid response", {
location: "OpenRouterEmbedder:_embedBatchWithRetries",
model,
dimensions: this.dimensions,
provider: this.specificProvider,
code,
err: msg,
})
const invalid = new Error(msg ?? "Invalid response from OpenRouter embedding endpoint") as HttpError
invalid.status = typeof code === "number" ? code : 422
throw invalid
}
// Convert base64 embeddings to float32 arrays
// Normalize base64 embeddings if OpenRouter returns them despite the float request.
const processedEmbeddings = response.data.map((item: EmbeddingItem) => {
if (typeof item.embedding === "string") {
const buffer = Buffer.from(item.embedding, "base64")
@@ -292,7 +306,7 @@ export class OpenRouterEmbedder implements IEmbedder {
const requestParams: any = {
input: testTexts,
model: modelToUse,
encoding_format: "base64",
encoding_format: "float",
}
if (this.dimensions !== undefined) {
@@ -315,6 +329,18 @@ export class OpenRouterEmbedder implements IEmbedder {
// Check if we got a valid response
if (!response?.data || response.data.length === 0) {
const err = response?.error
const msg = typeof err === "string" ? err : err?.message
const code = typeof err === "object" && err ? err.code : undefined
log.warn("OpenRouter embedder validation returned invalid response", {
location: "OpenRouterEmbedder:validateConfiguration",
model: modelToUse,
dimensions: this.dimensions,
provider: this.specificProvider,
dataCount: response?.data?.length ?? 0,
code,
err: msg,
})
return {
valid: false,
error: "Invalid response from OpenRouter embedding endpoint",
@@ -20,7 +20,7 @@ export function resolveEmbeddingProfile(
modelDimension?: number,
): EmbeddingProfile | undefined {
const id = modelId ?? getDefaultModelId(provider)
const dim = getModelDimension(provider, id) ?? parseDimension(modelDimension)
const dim = parseDimension(modelDimension) ?? getModelDimension(provider, id)
if (!dim) return undefined
return {
provider,
@@ -48,6 +48,7 @@ const profiles: Record<string, Record<string, ModelProfile>> = {
openrouter: {
"openai/text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 },
"openai/text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 },
"google/gemini-embedding-2-preview": { dimension: 3072, scoreThreshold: 0.35 },
},
"openai-compatible": {},
"vercel-ai-gateway": {
@@ -41,7 +41,7 @@ export interface LanguageParser {
}
}
function resolveModulePath(specifier: string): string | undefined {
export function resolveModulePath(specifier: string): string | undefined {
try {
return require.resolve(specifier)
} catch {
@@ -53,7 +53,7 @@ function uniquePaths(paths: Array<string | undefined>): string[] {
return [...new Set(paths.filter((item): item is string => !!item))]
}
function wasmDirectories(sourceDirectory?: string): string[] {
export function wasmDirectories(sourceDirectory?: string): string[] {
const baseDir = sourceDirectory || __dirname
const execDir = path.dirname(process.execPath)
const envDir = process.env.KILO_TREE_SITTER_WASM_DIR
@@ -80,7 +80,7 @@ function resolveFromDirectories(file: string, dirs: string[]): string | undefine
}
}
function resolveCoreRuntimeWasmPath(sourceDirectory?: string): string | undefined {
export function resolveCoreRuntimeWasmPath(sourceDirectory?: string): string | undefined {
const dirs = wasmDirectories(sourceDirectory)
const localPath = resolveFromDirectories("tree-sitter.wasm", dirs)
if (localPath) {
@@ -89,7 +89,7 @@ function resolveCoreRuntimeWasmPath(sourceDirectory?: string): string | undefine
return resolveModulePath("web-tree-sitter/tree-sitter.wasm")
}
function resolveLanguageWasmPath(langName: string, sourceDirectory?: string) {
export function resolveLanguageWasmPath(langName: string, sourceDirectory?: string) {
const dirs = wasmDirectories(sourceDirectory)
const fileName = `tree-sitter-${langName}.wasm`
const wasmPath = resolveFromDirectories(fileName, dirs)
@@ -83,6 +83,20 @@ describe("CodeIndexConfigManager", () => {
expect(cfg.currentModelDimension).toBe(2048)
})
test("uses configured dimension before static model metadata", () => {
const cfg = new CodeIndexConfigManager(
createInput({
embedderProvider: "openrouter",
openAiKey: undefined,
openRouterApiKey: "or-test",
modelId: "google/gemini-embedding-2-preview",
modelDimension: 1536,
}),
)
expect(cfg.currentModelDimension).toBe(1536)
})
describe("loadConfiguration restart checks", () => {
test("requires restart when model changes with same dimension", () => {
const cfg = new CodeIndexConfigManager(createInput({ modelId: "text-embedding-3-small" }))
@@ -68,14 +68,10 @@ describe("OpenRouterEmbedder", () => {
})
test("should create embeddings successfully", async () => {
// Create base64 encoded embedding with values that can be exactly represented in Float32
const testEmbedding = new Float32Array([0.25, 0.5, 0.75])
const base64String = Buffer.from(testEmbedding.buffer).toString("base64")
const mockResponse = {
data: [
{
embedding: base64String,
embedding: [0.25, 0.5, 0.75],
},
],
usage: {
@@ -91,7 +87,7 @@ describe("OpenRouterEmbedder", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test text"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
})
expect(result.embeddings).toHaveLength(1)
expect(result.embeddings[0]).toEqual([0.25, 0.5, 0.75])
@@ -99,6 +95,20 @@ describe("OpenRouterEmbedder", () => {
expect(result.usage?.totalTokens).toBe(5)
})
test("should not retry invalid responses without embedding data", async () => {
mockEmbeddingsCreate.mockResolvedValue({
error: {
code: 404,
message: "No successful provider responses.",
},
})
await expect(embedder.createEmbeddings(["test"])).rejects.toThrow(
"Embedding request failed after 3 attempts with status 404: No successful provider responses.",
)
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1)
})
test("should handle multiple texts", async () => {
const embedding1 = new Float32Array([0.25, 0.5])
const embedding2 = new Float32Array([0.75, 1.0])
@@ -156,7 +166,7 @@ describe("OpenRouterEmbedder", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test"],
model: customModel,
encoding_format: "base64",
encoding_format: "float",
})
})
@@ -187,7 +197,7 @@ describe("OpenRouterEmbedder", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
provider: {
order: [specificProvider],
only: [specificProvider],
@@ -221,7 +231,7 @@ describe("OpenRouterEmbedder", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
dimensions: 1024,
})
})
@@ -257,7 +267,7 @@ describe("OpenRouterEmbedder", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
})
})
})
@@ -271,13 +281,10 @@ describe("OpenRouterEmbedder", () => {
})
test("should validate configuration successfully", async () => {
const testEmbedding = new Float32Array([0.25, 0.5])
const base64String = Buffer.from(testEmbedding.buffer).toString("base64")
const mockResponse = {
data: [
{
embedding: base64String,
embedding: [0.25, 0.5],
},
],
usage: {
@@ -296,7 +303,7 @@ describe("OpenRouterEmbedder", () => {
{
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
},
{
timeout: REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
@@ -305,6 +312,20 @@ describe("OpenRouterEmbedder", () => {
)
})
test("should reject responses without embedding data", async () => {
mockEmbeddingsCreate.mockResolvedValue({
error: {
code: 404,
message: "No successful provider responses.",
},
})
const result = await embedder.validateConfiguration()
expect(result.valid).toBe(false)
expect(result.error).toBe("Invalid response from OpenRouter embedding endpoint")
})
test("should handle validation failure", async () => {
const authError = new Error("Invalid API key")
;(authError as any).status = 401
@@ -346,7 +367,7 @@ describe("OpenRouterEmbedder", () => {
{
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
provider: {
order: [specificProvider],
only: [specificProvider],
@@ -388,7 +409,7 @@ describe("OpenRouterEmbedder", () => {
{
input: ["test"],
model: defaultModel,
encoding_format: "base64",
encoding_format: "float",
dimensions: 1024,
},
{
@@ -149,11 +149,42 @@ describe("CodeIndexServiceFactory", () => {
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
input: ["hello"],
model: "openai/text-embedding-3-small",
encoding_format: "base64",
encoding_format: "float",
dimensions: 1024,
})
})
test("creates vector store for OpenRouter Gemini embedding preview", () => {
const factory = createFactory({
embedderProvider: "openrouter",
openAiKey: undefined,
openRouterApiKey: "or-test",
modelId: "google/gemini-embedding-2-preview",
vectorStoreProvider: "lancedb",
})
const store = factory.createVectorStore() as unknown as { vectorSize: number }
expect(store).toBeDefined()
expect(store.vectorSize).toBe(3072)
})
test("uses configured dimension before static model metadata for vector stores", () => {
const factory = createFactory({
embedderProvider: "openrouter",
openAiKey: undefined,
openRouterApiKey: "or-test",
modelId: "openai/text-embedding-3-small",
modelDimension: 1024,
vectorStoreProvider: "lancedb",
})
const store = factory.createVectorStore() as unknown as { vectorSize: number }
expect(store).toBeDefined()
expect(store.vectorSize).toBe(1024)
})
test("creates Kilo embedder with Cloud-provided model", async () => {
const factory = createFactory({
embedderProvider: "kilo",
@@ -177,6 +208,7 @@ describe("CodeIndexServiceFactory", () => {
input: ["hello"],
model: "mistralai/mistral-embed-2312",
encoding_format: "base64",
dimensions: 1024,
})
})
})
@@ -0,0 +1,32 @@
import { afterEach, describe, expect, test } from "bun:test"
import { mkdir, mkdtemp, rm, writeFile } from "fs/promises"
import { join } from "path"
import { tmpdir } from "os"
import { resolveCoreRuntimeWasmPath, resolveLanguageWasmPath } from "../../../src/tree-sitter/languageParser"
const env = "KILO_TREE_SITTER_WASM_DIR"
const prev = process.env[env]
describe("tree-sitter WASM resolution", () => {
afterEach(() => {
if (prev === undefined) delete process.env[env]
if (prev !== undefined) process.env[env] = prev
})
test("prefers installed CLI tree-sitter resources over module resolution", async () => {
const root = await mkdtemp(join(tmpdir(), "kilo-tree-sitter-wasm-"))
try {
const dir = join(root, "bin", "tree-sitter")
await mkdir(dir, { recursive: true })
await writeFile(join(dir, "tree-sitter.wasm"), "runtime")
await writeFile(join(dir, "tree-sitter-typescript.wasm"), "language")
process.env[env] = dir
expect(resolveCoreRuntimeWasmPath()).toBe(join(dir, "tree-sitter.wasm"))
expect(resolveLanguageWasmPath("typescript").wasmPath).toBe(join(dir, "tree-sitter-typescript.wasm"))
} finally {
await rm(root, { recursive: true, force: true })
}
})
})
@@ -5,7 +5,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/kilo-jetbrains" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-Pkilo.dev.log.level=debug" />
<option name="scriptParameters" value="-Pkilo.dev.log.level=debug -Pkilo.splitModeServerPort=0 -Pkilo.dev.storage.isolated=true" />
<option name="taskDescriptions">
<list />
</option>
@@ -25,4 +25,4 @@
<GradleCoverageDisabled>false</GradleCoverageDisabled>
<method v="2" />
</configuration>
</component>
</component>
+43 -1
View File
@@ -153,9 +153,17 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi
- The plugin spawns `kilo serve --port 0` (OS assigns random port) and reads stdout for `listening on http://...:(\d+)` to discover the port.
- A random 32-byte hex password is passed via `KILO_SERVER_PASSWORD` env var for Basic Auth.
- Key env vars: `KILO_CLIENT=jetbrains`, `KILO_PLATFORM=jetbrains`, `KILO_APP_NAME=kilo-code`, `KILO_ENABLE_QUESTION_TOOL=true`.
- Fixed env vars set on every spawn: `KILO_CLIENT=jetbrains`, `KILO_PLATFORM=jetbrains`, `KILO_APP_NAME=kilo-code`, `KILO_ENABLE_QUESTION_TOOL=true`, `KILO_DISABLE_CLAUDE_CODE=true`, `KILOCODE_FEATURE=jetbrains-plugin`.
- This is the same protocol used by the VS Code extension (`packages/kilo-vscode/src/services/cli-backend/server-manager.ts`).
### Dev Storage Isolation
- In development (`runIdeBackend` / `runIde`), the Gradle property `kilo.dev.storage.isolated=true` makes the backend set `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, and `XDG_CACHE_HOME` to `<worktree>/.kilo-dev/{data,config,state,cache}` before spawning the CLI. The worktree root comes from the `kilo.dev.worktree.root` JVM system property (auto-set by Gradle from the project directory).
- The checked-in `Run IDE (Backend)` run configuration enables isolation by default (`-Pkilo.dev.storage.isolated=true`). Developers can disable it by passing `-Pkilo.dev.storage.isolated=false`.
- Use standard `XDG_*_HOME` env vars for this isolation. Do not introduce custom `KILO_DATA_DIR`, `KILO_GLOBAL_CONFIG_DIR`, `KILO_STATE_DIR`, or `KILO_CACHE_DIR` env vars — the CLI core already respects `XDG_*_HOME` via `xdg-basedir`.
- The `.kilo-dev/` directory is gitignored and created automatically on first run.
- The implementation lives in `KiloBackendCliManager.buildEnv()` / `devStorageEnv()`. Tests: `KiloBackendCliManagerEnvTest`.
## Build and Verification
- **Typecheck**: `bun run typecheck` or `./gradlew typecheck` from `packages/kilo-jetbrains/` — compiles all Kotlin sources including the generated API client. Does NOT require CLI binaries.
@@ -163,6 +171,7 @@ For blocking I/O in coroutines, move the dispatcher switch inside the callee usi
- **Gradle only**: `./gradlew buildPlugin` from `packages/kilo-jetbrains/` (requires CLI binaries already present in `backend/build/generated/cli/`; run `bun run build --prepare-cli` first).
- **Via Turbo**: `bun turbo build --filter=@kilocode/kilo-jetbrains` from repo root.
- **Run in sandbox**: `./gradlew runIde` — launches sandboxed IntelliJ with the plugin. Does NOT build CLI binaries.
- **Run split backend**: `./gradlew runIdeBackend` — if it exits shortly after startup, check for an orphaned Java process from a previous backend run and kill it before restarting.
- **Test split mode**: `./gradlew generateSplitModeRunConfigurations` creates a "Run IDE (Split Mode)" config that starts both frontend and backend processes locally. Emulate latency via the Split Mode widget (requires internal mode: `-Didea.is.internal=true`).
## UI Guidelines
@@ -357,6 +366,39 @@ For common spacing lookups, prefer `JBUI.CurrentTheme` area-specific insets (e.g
| Simple `BorderLayout` panels | `JBUI.Panels.simplePanel(...)`, `BorderLayoutPanel` |
| Simple vertical custom Swing groups | `VerticalLayout` |
| Fluent platform panels | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()`, `.withBackground(...)` |
| Single-component alignment wrapper | `ai.kilocode.client.ui.layout.Align` — see section below |
### Align — Single-Component Alignment Wrapper
Use `Align` (`ai.kilocode.client.ui.layout.Align`) when a single Swing component must be positioned inside available space without adding visual chrome. It is a transparent, no-border, no-color `JPanel(null)` that lays out its one child according to independent horizontal (`HAlign`) and vertical (`VAlign`) modes. `CenterShrinkPanel` has been removed; use `child.align(HAlign.CENTER, VAlign.CENTER)` as a direct replacement.
**Alignment modes:**
| Mode | Axis | Layout behavior | Wrapper size contribution |
|---|---|---|---|
| `HAlign.TRACK` / `VAlign.TRACK` | either | Child always fills all available space; ignores child min/preferred/max | Zero (wrapper reports insets only on that axis) |
| `HAlign.FIT` / `VAlign.FIT` | either | Child fills available space clamped to child's effective `[min, max]` range | Child min/preferred/max respected |
| `HAlign.LEFT` / `VAlign.TOP` | H / V | Child placed at left/top edge at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
| `HAlign.CENTER` / `VAlign.CENTER` | H / V | Child centered at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
| `HAlign.RIGHT` / `VAlign.BOTTOM` | H / V | Child placed at right/bottom edge at bounded preferred size; shrinks to available when necessary | Child min/preferred/max respected |
"Bounded preferred" means the child's preferred size coerced into the effective `[min, max]` range. If available space is smaller than the effective minimum, the layout shrinks the child to available space to avoid overflow.
**Factory extension** on `Component`:
```kotlin
child.align(HAlign.LEFT, VAlign.TOP) // left-aligned, top-pinned
child.align(HAlign.CENTER, VAlign.CENTER) // centered (replaces CenterShrinkPanel)
child.align(HAlign.TRACK, VAlign.CENTER) // fill width, center vertically
child.align(HAlign.TRACK, VAlign.TRACK) // fill all available space
```
**Rules:**
- Prefer `child.align(h, v)` over creating one-off `JPanel(FlowLayout(...))` or `BorderLayoutPanel` wrappers just to control alignment.
- Use `TRACK` when the child must occupy all available space on an axis and must not reserve any space in the parent's size negotiation on that axis. Use `FIT` when you want to fill available space but still respect child min/max constraints.
- All non-TRACK modes include the child's min, preferred, and max sizes in the wrapper's own min/preferred/max size. This means parent layout managers see the child constraints through the wrapper.
- Do not use `Align` for spacing, padding, borders, colors, or multi-child layout — use `JBUI.Borders.empty(...)`, `UiStyle.Gap`, or an appropriate layout manager for those concerns.
### IntelliJ UI Surfaces
+82 -5
View File
@@ -9,11 +9,33 @@ AI coding agent plugin for JetBrains IDEs.
### Prerequisites
- **Bun** -- used to build CLI binaries and run build scripts
- **JDK 21+** -- required by Gradle and the IntelliJ Platform SDK
- **JDK 21+** -- required by Gradle and the IntelliJ Platform SDK. Check with `java -version`. The preferred way to install is via [SDKMAN](https://sdkman.io/install):
```bash
# Install SDKMAN (if not already installed)
curl -s "https://get.sdkman.io" | bash
# Install and activate Java 21 (Eclipse Temurin)
sdk install java 21-tem
sdk use java 21-tem
```
- **IntelliJ IDEA** -- to run the plugin in a sandboxed IDE
---
## Fresh worktree setup
When working in a git worktree (e.g. via the Agent Manager), run `bun install` from the repo root before building or running Gradle tasks:
```bash
bun install
```
This installs Node dependencies required by the build scripts, including `script/build.ts` which prepares CLI binaries.
---
## Open in IntelliJ
When you open the monorepo root in IntelliJ IDEA, the Gradle project at `packages/kilo-jetbrains/` should be auto-detected via `.idea/gradle.xml`. If not, link it manually: **File > Settings > Build Tools > Gradle > +** and select `packages/kilo-jetbrains/settings.gradle.kts`.
@@ -60,11 +82,58 @@ See [RELEASING.md](RELEASING.md) for the full release process, including how to
## Run the plugin
Use the `runIde` Gradle task (available in the Gradle tool window or via the "Run JetBrains Plugin" run configuration) to launch a sandboxed IntelliJ instance with the plugin installed.
Use the `runIde` Gradle task (available in the Gradle tool window or via `./gradlew runIde` from `packages/kilo-jetbrains/`) to launch a sandboxed IntelliJ instance with the plugin installed.
On a fresh worktree, `runIde` now checks `backend/build/generated/cli/cli/` first. If the local-platform CLI binary is missing, it runs the standard single-binary generation flow and copies the result into the backend resources automatically.
`runIde` does not prepare the CLI binary automatically. Run `bun run build --prepare-cli` from `packages/kilo-jetbrains/` first to copy the local-platform binary into `backend/build/generated/cli/cli/`.
That bootstrap is local-development only. Production packaging still requires running `bun run build:production` so all platform binaries are present.
Production packaging still requires running `bun run build:production` so all platform binaries are present.
### Run the split backend
Use the checked-in `Run IDE (Backend)` run configuration (or `./gradlew runIdeBackend`) to launch just the backend half of a split-mode session. It prepares the local-platform CLI binary automatically when `backend/build/generated/cli/cli/` does not contain the expected binary.
If `Run IDE (Backend)` exits shortly after startup, check for an orphaned Java process from a previous backend run and kill it before restarting the backend.
Use `Run IDE (Split Mode)` to launch both halves at once (composes `Run IDE (Backend)` + `Run IDE (Frontend)`).
### Backend Gradle properties
All properties below are passed with `-P` on the Gradle command line or in the run configuration's script parameters field.
| Property | Default | Description |
|---|---|---|
| `kilo.splitModeServerPort` | random high port | Backend split-mode server port. `0` or omitted picks a random port from 49152-65535. |
| `kilo.dev.storage.isolated` | `false` | When `true`, CLI runs with `XDG_*_HOME` pointing to `.kilo-dev/` in the worktree root, fully isolating dev storage from your real Kilo installation. Enabled by default in `Run IDE (Backend)`. |
| `kilo.dev.worktree.root` | monorepo root | Worktree root used to resolve `.kilo-dev/`. Auto-detected from the Gradle project directory; override only when the auto-detection is wrong. |
| `kilo.bun.path` | `bun` on `$PATH` | Absolute path to Bun. Set this when IntelliJ-launched Gradle cannot find Bun automatically. |
Example with a fixed split-mode port:
```text
-Pkilo.dev.log.level=debug -Pkilo.splitModeServerPort=12345
```
### Dev storage isolation
When `kilo.dev.storage.isolated=true`, the CLI subprocess receives standard `XDG_*_HOME` env vars pointing under `.kilo-dev/` in the worktree root:
```
.kilo-dev/
data/ -> XDG_DATA_HOME (CLI uses .../data/kilo for sessions, logs, ...)
config/ -> XDG_CONFIG_HOME (CLI uses .../config/kilo for global config)
state/ -> XDG_STATE_HOME (CLI uses .../state/kilo for state)
cache/ -> XDG_CACHE_HOME (CLI uses .../cache/kilo for cache, bin)
```
This keeps all development data isolated from your real Kilo installation. The `.kilo-dev/` directory is gitignored and created automatically on first run.
The `Run IDE (Backend)` run configuration enables this by default. To disable it:
```text
-Pkilo.dev.storage.isolated=false
```
---
### Debug logging properties
@@ -115,7 +184,15 @@ Use `off` first. Switch to `preview` only when you need prompt or tool payload h
## Run Gradle directly
You can run `./gradlew buildPlugin` directly for local development. Gradle will auto-generate the current-platform CLI binary if `backend/build/generated/cli/` is missing.
For direct local packaging, run:
```bash
bun run build
```
This prepares the local CLI binary and then runs `./gradlew buildPlugin`.
If you run `./gradlew buildPlugin` directly, Gradle verifies CLI binaries are present but does not build them first. Run `bun run build --prepare-cli` beforehand if the binaries are missing.
For production verification:
@@ -1,3 +1,5 @@
import normalization.NormalizeOpenApiSpecTask
plugins {
alias(libs.plugins.rpc)
alias(libs.plugins.kotlin)
@@ -11,6 +13,7 @@ kotlin {
}
val generatedApi = layout.buildDirectory.dir("generated/openapi/src/main/kotlin")
val rawSpec = layout.buildDirectory.file("generated/openapi-spec/openapi.raw.json")
val generatedSpec = layout.buildDirectory.file("generated/openapi-spec/openapi.json")
sourceSets {
@@ -24,6 +27,13 @@ val generateOpenApiSpec by tasks.registering(GenerateOpenApiSpecTask::class) {
description = "Generate CLI OpenAPI spec into the build directory"
opencodeDir.set(rootProject.layout.projectDirectory.dir("../opencode"))
serverSrcDir.set(rootProject.layout.projectDirectory.dir("../opencode/src/server"))
spec.set(rawSpec)
}
val normalizeOpenApiSpec by tasks.registering(NormalizeOpenApiSpecTask::class) {
description = "Normalize upstream CLI OpenAPI metadata before Kotlin client generation"
dependsOn(generateOpenApiSpec)
input.set(rawSpec)
spec.set(generatedSpec)
}
@@ -64,7 +74,7 @@ openApiGenerate {
}
tasks.named("openApiGenerate") {
dependsOn(generateOpenApiSpec)
dependsOn(normalizeOpenApiSpec)
}
val fixGeneratedApi by tasks.registering(FixGeneratedApiTask::class) {
@@ -76,9 +86,23 @@ tasks.named("compileKotlin") {
dependsOn(fixGeneratedApi)
}
tasks.named("compileTestKotlin") {
dependsOn(fixGeneratedApi)
}
val cliDir = layout.buildDirectory.dir("generated/cli/cli")
val production = providers.gradleProperty("production").map { it.toBoolean() }.orElse(false)
val prepareLocalCli by tasks.registering(PrepareLocalCliTask::class) {
description = "Prepare the local-platform CLI binary for JetBrains backend runs"
root.set(rootProject.layout.projectDirectory)
dir.set(cliDir)
bunPath.convention(
providers.gradleProperty("kilo.bun.path")
.orElse(providers.environmentVariable("BUN_EXE"))
)
}
val requiredPlatforms = listOf(
"darwin-arm64",
"darwin-x64",
@@ -13,6 +13,9 @@ import ai.kilocode.jetbrains.api.model.Config
import ai.kilocode.jetbrains.api.model.ConfigWarnings200ResponseInner
import ai.kilocode.jetbrains.api.model.KiloNotifications200ResponseInner
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
import ai.kilocode.jetbrains.api.model.ProviderOauthAuthorizeRequest
import ai.kilocode.jetbrains.api.model.ProviderOauthCallbackRequest
import ai.kilocode.rpc.dto.DeviceAuthDto
import ai.kilocode.rpc.dto.HealthDto
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
@@ -27,10 +30,18 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import okhttp3.OkHttpClient
import kotlinx.coroutines.withContext
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.util.concurrent.CopyOnWriteArrayList
@@ -550,6 +561,89 @@ class KiloBackendAppService private constructor(
_appState.value = KiloAppState.Disconnected
}
/**
* Refresh the user profile from the CLI backend.
* Returns the latest profile data, or null when not logged in.
* Updates the current [KiloAppState.Ready] profile in-place if the app is ready.
*/
suspend fun refreshProfile(): KiloProfile200Response? {
val result = fetchProfile()
val fresh = result.value
val current = _appState.value
if (current is KiloAppState.Ready) {
setAppReady(current.data.copy(profile = fresh))
}
profile = fresh
return fresh
}
/**
* Start the Kilo device auth login flow.
* Returns [DeviceAuthDto] containing the verification URL and code for display in the UI.
*/
suspend fun startLogin(directory: String?): DeviceAuthDto {
val client = connection.api ?: throw IllegalStateException("Not connected")
val body = ProviderOauthAuthorizeRequest(method = 0.0)
val response = client.providerOauthAuthorize(providerID = "kilo", directory = directory, providerOauthAuthorizeRequest = body)
val match = response.instructions.let { Regex("""code:\s*(\S+)""", RegexOption.IGNORE_CASE).find(it) }
return DeviceAuthDto(
code = match?.groupValues?.get(1),
verificationUrl = response.url,
expiresIn = 900,
)
}
/**
* Complete the Kilo device auth login flow.
* Blocks until the user completes authentication on the browser side.
* Returns the user profile on success, or null if the login could not be completed.
*/
suspend fun completeLogin(directory: String?): KiloProfile200Response? {
val client = connection.api ?: throw IllegalStateException("Not connected")
client.providerOauthCallback(providerID = "kilo", directory = directory, providerOauthCallbackRequest = ProviderOauthCallbackRequest(method = 0.0))
return refreshProfile()
}
/**
* Log out from Kilo Gateway.
* Removes credentials and clears the profile from app state.
*/
suspend fun logout(): Boolean {
val client = connection.api ?: throw IllegalStateException("Not connected")
val result = client.authRemove(providerID = "kilo")
val current = _appState.value
if (current is KiloAppState.Ready) {
profile = null
setAppReady(current.data.copy(profile = null))
}
return result
}
/**
* Switch the active account context.
* Pass null for personal account, an organization ID for org context.
* Returns the updated profile after the switch.
*/
suspend fun setOrganization(organizationId: String?): KiloProfile200Response? {
val http = connection.apiClient ?: throw IllegalStateException("Not connected")
val body = JsonObject(
mapOf("organizationId" to (organizationId?.let { JsonPrimitive(it) } ?: JsonNull)),
).toString()
val request = Request.Builder()
.url("http://127.0.0.1:$port/kilo/organization")
.header("Accept", "application/json")
.post(body.toRequestBody("application/json".toMediaType()))
.build()
withContext(Dispatchers.IO) {
http.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IllegalStateException("Organization switch failed: HTTP ${response.code} ${response.message}")
}
}
}
return refreshProfile()
}
override fun dispose() {
watcher?.cancel()
watcher = null
@@ -9,10 +9,6 @@ import ai.kilocode.rpc.dto.ModelStateDto
import ai.kilocode.rpc.dto.ModelVariantUpdateDto
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import okhttp3.Request
import java.nio.file.Files
@@ -29,7 +25,6 @@ class KiloBackendModelStateManager(
private val DEFAULT_DIR = Path.of(System.getProperty("user.home"), ".local", "state", "kilo")
}
private val json = Json { ignoreUnknownKeys = true }
private val mutex = Mutex()
private var client: OkHttpClient? = null
@@ -122,8 +117,7 @@ class KiloBackendModelStateManager(
return null
}
val raw = response.body?.string() ?: return null
val state = json.parseToJsonElement(raw).jsonObject["state"]?.jsonPrimitive?.contentOrNull
val dir = state?.let(Path::of) ?: DEFAULT_DIR
val dir = KiloCliDataParser.parsePathState(raw)?.let(Path::of) ?: DEFAULT_DIR
Files.createDirectories(dir)
dir.resolve("model.json").also { file = it }
}
@@ -121,21 +121,52 @@ class KiloBackendCliManager(
return target
}
// Must be called from a background thread — devStorageEnv() performs blocking I/O (mkdirs).
internal fun buildEnv(pwd: String, base: Map<String, String> = System.getenv()): Map<String, String> = buildMap {
putAll(base)
put("KILO_SERVER_PASSWORD", pwd)
put("KILO_CLIENT", "jetbrains")
put("KILO_ENABLE_QUESTION_TOOL", "true")
put("KILO_PLATFORM", "jetbrains")
put("KILO_APP_NAME", "kilo-code")
put("KILO_DISABLE_CLAUDE_CODE", "true")
put("KILOCODE_FEATURE", "jetbrains-plugin")
ideEnv().forEach { (k, v) -> put(k, v) }
devStorageEnv()?.forEach { (k, v) -> put(k, v) }
}
private fun devStorageEnv(): Map<String, String>? {
val enabled = System.getProperty("kilo.dev.storage.isolated", "false").toBoolean()
if (!enabled) return null
val root = System.getProperty("kilo.dev.worktree.root") ?: run {
log.warn("kilo.dev.storage.isolated=true but kilo.dev.worktree.root is not set; skipping dev storage isolation")
return null
}
val dev = File(root, ".kilo-dev")
val data = File(dev, "data")
val config = File(dev, "config")
val state = File(dev, "state")
val cache = File(dev, "cache")
for (dir in listOf(data, config, state, cache)) {
if (!dir.mkdirs() && !dir.isDirectory) {
log.warn("Failed to create dev storage dir ${dir.absolutePath}; skipping dev storage isolation")
return null
}
}
log.info("Dev storage isolation enabled under ${dev.absolutePath}")
return mapOf(
"XDG_DATA_HOME" to data.absolutePath,
"XDG_CONFIG_HOME" to config.absolutePath,
"XDG_STATE_HOME" to state.absolutePath,
"XDG_CACHE_HOME" to cache.absolutePath,
)
}
private suspend fun spawn(cli: File): CliServer.State =
withContext(Dispatchers.IO) {
val pwd = generatePassword()
val env = buildMap {
putAll(System.getenv())
put("KILO_SERVER_PASSWORD", pwd)
put("KILO_CLIENT", "jetbrains")
put("KILO_ENABLE_QUESTION_TOOL", "true")
put("KILO_PLATFORM", "jetbrains")
put("KILO_APP_NAME", "kilo-code")
put("KILO_DISABLE_CLAUDE_CODE", "true")
put("KILOCODE_FEATURE", "jetbrains-plugin")
ideEnv().forEach { (k, v) -> put(k, v) }
}
val env = buildEnv(pwd)
val cmd = listOf(cli.absolutePath, "serve", "--port", "0")
val builder = ProcessBuilder(cmd)
@@ -1,5 +1,10 @@
package ai.kilocode.backend.cli
import ai.kilocode.backend.workspace.CommandInfo
import ai.kilocode.backend.workspace.ModelInfo
import ai.kilocode.backend.workspace.ModelLimitInfo
import ai.kilocode.backend.workspace.ProviderData
import ai.kilocode.backend.workspace.ProviderInfo
import ai.kilocode.rpc.dto.ChatEventDto
import ai.kilocode.rpc.dto.CloudSessionDto
import ai.kilocode.rpc.dto.CloudSessionListDto
@@ -13,6 +18,7 @@ import ai.kilocode.rpc.dto.ModelSelectionDto
import ai.kilocode.rpc.dto.ModelStateDto
import ai.kilocode.rpc.dto.PartDto
import ai.kilocode.rpc.dto.PermissionAlwaysRulesDto
import ai.kilocode.rpc.dto.PermissionFileDiffDto
import ai.kilocode.rpc.dto.PermissionReplyDto
import ai.kilocode.rpc.dto.PermissionRequestDto
import ai.kilocode.rpc.dto.PartTimeDto
@@ -286,6 +292,45 @@ object KiloCliDataParser {
)
}
/**
* Parse a provider catalog response (`GET /provider`) into [ProviderData].
* Throws if [raw] is not a valid JSON object (lets the workspace loading
* catch the exception and surface it as a LoadError).
*/
fun parseProviders(raw: String): ProviderData {
val obj = json.parseToJsonElement(raw).jsonObject
return ProviderData(
providers = obj["all"]?.jsonArray?.map { parseProvider(it.jsonObject) } ?: emptyList(),
connected = obj["connected"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
defaults = obj["default"]?.jsonObject?.mapValues { (_, v) -> v.jsonPrimitive.content } ?: emptyMap(),
)
}
/**
* Parse a command list response (`GET /command`) into a list of [CommandInfo].
* The `template` field is intentionally ignored — CLI commands can return lazy
* promise objects (`{}`) for that field, which must not crash JetBrains startup.
*/
fun parseCommands(raw: String): List<CommandInfo> =
json.parseToJsonElement(raw).jsonArray.map { item ->
val obj = item.jsonObject
CommandInfo(
name = obj.str("name") ?: "",
description = obj.str("description"),
source = obj.str("source"),
hints = obj["hints"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
)
}
/**
* Extract the `state` directory path from a `/path` response.
* Returns `null` when the field is missing, not a JSON string, or the JSON is malformed.
*/
fun parsePathState(raw: String): String? {
val prim = runCatching { tryParseObject(raw)?.get("state")?.jsonPrimitive }.getOrNull() ?: return null
return if (prim.isString) prim.content else null
}
fun parseModelState(raw: String): ModelStateDto {
val obj = tryParseObject(raw) ?: return ModelStateDto()
return ModelStateDto(
@@ -327,6 +372,14 @@ object KiloCliDataParser {
}
val sb = StringBuilder()
sb.append("""{"parts":[$parts]""")
val msg = prompt.messageID
if (msg != null) {
sb.append(""","messageID":${escape(msg)}""")
}
val reply = prompt.noReply
if (reply != null) {
sb.append(""","noReply":$reply""")
}
val pid = prompt.providerID
val mid = prompt.modelID
if (pid != null && mid != null) {
@@ -441,10 +494,16 @@ object KiloCliDataParser {
internal fun parseError(obj: JsonObject): MessageErrorDto {
val type = obj.str("type") ?: obj.str("name") ?: "unknown"
val data = obj["data"]?.jsonObject
val msg = obj.str("message")
?: obj["data"]?.jsonObject?.str("message")
?: data?.str("message")
?: obj.str("error")
return MessageErrorDto(type, msg)
return MessageErrorDto(
type,
msg,
statusCode = data?.long("statusCode")?.safeInt(),
responseBody = data?.str("responseBody"),
)
}
internal fun parsePermissionRequest(obj: JsonObject): PermissionRequestDto? {
@@ -453,11 +512,27 @@ object KiloCliDataParser {
val permission = obj.str("permission") ?: return null
val patterns = obj["patterns"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList()
val always = obj["always"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList()
val meta = obj["metadata"]?.jsonObject?.let { m ->
m.entries.associate { (k, v) -> k to (v.jsonPrimitive.contentOrNull ?: "") }
} ?: emptyMap()
val ref = toolRef(obj)
return PermissionRequestDto(id, sid, permission, patterns, meta, always, ref)
val metaObj = obj["metadata"].obj()
val meta = metaObj?.entries?.mapNotNull { (key, value) ->
val text = value.scalar() ?: return@mapNotNull null
key to text
}?.toMap() ?: emptyMap()
val path = metaObj.path()
val diffs = metaObj.permissionDiffs(path)
return PermissionRequestDto(
id = id,
sessionID = sid,
permission = permission,
patterns = patterns,
metadata = meta,
always = always,
tool = toolRef(obj),
message = obj.str("message") ?: metaObj?.str("message"),
command = metaObj?.str("command") ?: obj.str("command"),
rules = metaObj.rules(),
filePath = path,
fileDiffs = diffs,
)
}
internal fun parseQuestionRequest(obj: JsonObject): QuestionRequestDto? {
@@ -520,6 +595,49 @@ object KiloCliDataParser {
"modelID" to JsonPrimitive(item.modelID),
))
// ================================================================
// Internal — provider/catalog parsing
// ================================================================
private val EFFORT_ORDER = listOf("none", "minimal", "low", "medium", "high", "xhigh", "max")
.withIndex().associate { it.value to it.index }
private fun parseProvider(obj: JsonObject) = ProviderInfo(
id = obj.str("id") ?: "",
name = obj.str("name") ?: "",
source = obj.str("source"),
models = obj["models"]?.jsonObject?.mapValues { (id, v) -> parseModel(id, v.jsonObject) } ?: emptyMap(),
)
private fun parseModel(id: String, obj: JsonObject): ModelInfo {
val cap = obj["capabilities"]?.jsonObject
val limit = obj["limit"]?.jsonObject
return ModelInfo(
id = obj.str("id") ?: id,
name = obj.str("name") ?: id,
attachment = cap.bool("attachment"),
reasoning = cap.bool("reasoning"),
temperature = cap.bool("temperature"),
toolCall = cap.bool("toolcall"),
free = obj.bool("isFree"),
status = obj.str("status"),
recommendedIndex = obj.num("recommendedIndex"),
variants = parseVariants(obj),
limit = limit?.let {
ModelLimitInfo(
context = it.long("context") ?: 0,
input = it.long("input"),
output = it.long("output") ?: 0,
)
},
)
}
private fun parseVariants(obj: JsonObject): List<String> {
val keys = obj["variants"]?.jsonObject?.keys?.toList() ?: return emptyList()
return keys.sortedWith(compareBy<String> { EFFORT_ORDER[it] ?: Int.MAX_VALUE }.thenBy { it })
}
private fun parseSessionObject(obj: JsonObject): SessionDto {
val time = obj["time"]?.jsonObject
val summary = obj["summary"]?.jsonObject
@@ -601,6 +719,11 @@ object KiloCliDataParser {
/**
* Build the JSON body for `POST /permission/{requestID}/reply`.
*/
internal fun parseRulesJson(text: String): List<String> {
val arr = runCatching { json.parseToJsonElement(text).jsonArray }.getOrNull() ?: return listOf(text)
return arr.mapNotNull { runCatching { it.jsonPrimitive.contentOrNull }.getOrNull() }
}
fun buildPermissionReplyJson(reply: PermissionReplyDto): String {
val sb = StringBuilder()
sb.append("""{"reply":${escape(reply.reply)}""")
@@ -669,6 +792,70 @@ object KiloCliDataParser {
}
}
// Permission metadata helpers
private fun JsonElement?.obj(): JsonObject? = runCatching { this?.jsonObject }.getOrNull()
private fun JsonElement?.arr(): JsonArray? = runCatching { this?.jsonArray }.getOrNull()
private fun JsonObject?.path(): String? {
if (this == null) return null
return str("filepath") ?: str("filePath") ?: str("file") ?: str("path")
}
private fun JsonObject?.rules(): List<String> {
if (this == null) return emptyList()
val raw = this["rules"] ?: return emptyList()
val arr = raw.arr()
if (arr != null) {
return arr.mapNotNull { it.jsonPrimitive.contentOrNull }
}
val text = runCatching { raw.jsonPrimitive.contentOrNull }.getOrNull() ?: return emptyList()
if (text.startsWith("[")) {
return runCatching {
KiloCliDataParser.parseRulesJson(text)
}.getOrElse { listOf(text) }
}
return listOf(text)
}
private fun JsonObject?.permissionDiffs(path: String?): List<PermissionFileDiffDto> {
if (this == null) return emptyList()
val filediff = this["filediff"].obj()
if (filediff != null) {
val file = filediff.str("file") ?: filediff.str("relativePath") ?: path ?: return emptyList()
return listOf(
PermissionFileDiffDto(
file = file,
patch = filediff.str("patch"),
before = filediff.str("before"),
after = filediff.str("after"),
additions = filediff.long("additions")?.safeInt() ?: 0,
deletions = filediff.long("deletions")?.safeInt() ?: 0,
)
)
}
val files = this["files"].arr()
if (files != null) {
return files.mapNotNull { elem ->
val item = elem.obj() ?: return@mapNotNull null
val file = item.str("relativePath") ?: item.str("filePath") ?: item.str("file") ?: return@mapNotNull null
PermissionFileDiffDto(
file = file,
patch = item.str("patch"),
before = item.str("before"),
after = item.str("after"),
additions = item.long("additions")?.safeInt() ?: 0,
deletions = item.long("deletions")?.safeInt() ?: 0,
)
}
}
val diff = str("diff")
if (diff != null) {
return listOf(PermissionFileDiffDto(file = path ?: "patch", patch = diff))
}
return emptyList()
}
// JsonObject convenience extensions
private fun JsonObject.str(key: String): String? =
this[key]?.jsonPrimitive?.contentOrNull
@@ -679,6 +866,9 @@ private fun JsonObject.num(key: String): Double? =
private fun JsonObject.long(key: String): Long? =
this[key]?.jsonPrimitive?.longOrNull
private fun JsonObject?.bool(key: String): Boolean =
this?.get(key)?.jsonPrimitive?.booleanOrNull ?: false
private fun Long.safeInt() = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
private fun JsonObject?.map(key: String): Map<String, String> {
@@ -11,10 +11,12 @@ import ai.kilocode.backend.app.ProfileResult
import ai.kilocode.jetbrains.api.model.AgentConfig
import ai.kilocode.jetbrains.api.model.Config
import ai.kilocode.jetbrains.api.model.ConfigAgent
import ai.kilocode.jetbrains.api.model.KiloProfile200Response
import ai.kilocode.rpc.dto.AgentConfigDto
import ai.kilocode.rpc.dto.ConfigDto
import ai.kilocode.rpc.KiloAppRpcApi
import ai.kilocode.rpc.dto.ConfigWarningDto
import ai.kilocode.rpc.dto.DeviceAuthDto
import ai.kilocode.rpc.dto.HealthDto
import ai.kilocode.rpc.dto.KiloAppStateDto
import ai.kilocode.rpc.dto.KiloAppStatusDto
@@ -24,6 +26,9 @@ import ai.kilocode.rpc.dto.ModelFavoriteUpdateDto
import ai.kilocode.rpc.dto.ModelSelectionUpdateDto
import ai.kilocode.rpc.dto.ModelStateDto
import ai.kilocode.rpc.dto.ModelVariantUpdateDto
import ai.kilocode.rpc.dto.ProfileBalanceDto
import ai.kilocode.rpc.dto.ProfileDto
import ai.kilocode.rpc.dto.ProfileOrganizationDto
import ai.kilocode.rpc.dto.ProfileStatusDto
import com.intellij.openapi.components.service
import kotlinx.coroutines.flow.Flow
@@ -63,6 +68,17 @@ class KiloAppRpcApiImpl : KiloAppRpcApi {
override suspend fun updateModelVariant(update: ModelVariantUpdateDto): ModelStateDto = app.models.variant(update)
override suspend fun refreshProfile(): ProfileDto? = app.refreshProfile()?.let(::profileDto)
override suspend fun startLogin(directory: String?): DeviceAuthDto = app.startLogin(directory)
override suspend fun completeLogin(directory: String?): ProfileDto? = app.completeLogin(directory)?.let(::profileDto)
override suspend fun logout(): Boolean = app.logout()
override suspend fun setOrganization(organizationId: String?): ProfileDto? =
app.setOrganization(organizationId)?.let(::profileDto)
private fun dto(state: KiloAppState): KiloAppStateDto =
appStateDto(state)
}
@@ -85,6 +101,7 @@ internal fun appStateDto(state: KiloAppState): KiloAppStateDto =
),
warnings = state.data.warnings.map(::warning),
config = config(state.data.config),
profile = state.data.profile?.let(::profileDto),
)
is KiloAppState.Error -> KiloAppStateDto(
status = KiloAppStatusDto.ERROR,
@@ -93,6 +110,16 @@ internal fun appStateDto(state: KiloAppState): KiloAppStateDto =
)
}
internal fun profileDto(p: KiloProfile200Response): ProfileDto = ProfileDto(
email = p.profile.email,
name = p.profile.name,
organizations = p.profile.organizations.orEmpty().map { org ->
ProfileOrganizationDto(id = org.id, name = org.name, role = org.role)
},
balance = p.balance?.let { ProfileBalanceDto(balance = it.balance) },
currentOrgId = p.currentOrgId,
)
private fun progress(p: LoadProgress) = LoadProgressDto(
config = p.config,
notifications = p.notifications,
@@ -3,20 +3,12 @@ package ai.kilocode.backend.workspace
import ai.kilocode.backend.app.KiloBackendSessionManager
import ai.kilocode.backend.app.LoadError
import ai.kilocode.backend.app.SseEvent
import ai.kilocode.backend.cli.KiloCliDataParser
import ai.kilocode.log.KiloLog
import ai.kilocode.jetbrains.api.client.DefaultApi
import ai.kilocode.jetbrains.api.model.Agent
import ai.kilocode.rpc.dto.SessionDto
import ai.kilocode.rpc.dto.SessionListDto
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.longOrNull
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -57,10 +49,6 @@ class KiloBackendWorkspace(
companion object {
private const val MAX_RETRIES = 3
private const val RETRY_DELAY_MS = 1000L
private val json = Json { ignoreUnknownKeys = true }
private val EFFORT_ORDER = listOf("none", "minimal", "low", "medium", "high", "xhigh", "max")
.withIndex()
.associate { it.value to it.index }
}
private val _state = MutableStateFlow<KiloWorkspaceState>(KiloWorkspaceState.Pending)
@@ -219,7 +207,7 @@ class KiloBackendWorkspace(
private fun fetchProviders(): FetchResult<ProviderData> =
try {
FetchResult.ok(parseProviders(fetch("/provider?directory=${encode(directory)}")))
FetchResult.ok(KiloCliDataParser.parseProviders(fetch("/provider?directory=${encode(directory)}")))
} catch (e: Exception) {
log.warn("Providers fetch failed: ${e.message}", e)
FetchResult.fail("providers", e)
@@ -242,14 +230,7 @@ class KiloBackendWorkspace(
private fun fetchCommands(): FetchResult<List<CommandInfo>> =
try {
FetchResult.ok(api.commandList(directory = directory).map { c ->
CommandInfo(
name = c.name,
description = c.description,
source = c.source?.value,
hints = c.hints,
)
})
FetchResult.ok(KiloCliDataParser.parseCommands(fetch("/command?directory=${encode(directory)}")))
} catch (e: Exception) {
log.warn("Commands fetch failed: ${e.message}", e)
FetchResult.fail("commands", e)
@@ -282,53 +263,8 @@ class KiloBackendWorkspace(
deprecated = a.deprecated,
)
private fun parseProviders(raw: String): ProviderData {
val obj = json.parseToJsonElement(raw).jsonObject
return ProviderData(
providers = obj["all"]?.jsonArray?.map { provider(it.jsonObject) } ?: emptyList(),
connected = obj["connected"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
defaults = obj["default"]?.jsonObject?.mapValues { (_, value) -> value.jsonPrimitive.content } ?: emptyMap(),
)
}
private fun provider(obj: JsonObject) = ProviderInfo(
id = obj.str("id") ?: "",
name = obj.str("name") ?: "",
source = obj.str("source"),
models = obj["models"]?.jsonObject?.mapValues { (id, value) -> model(id, value.jsonObject) } ?: emptyMap(),
)
private fun model(id: String, obj: JsonObject): ModelInfo {
val cap = obj["capabilities"]?.jsonObject
val limit = obj["limit"]?.jsonObject
return ModelInfo(
id = obj.str("id") ?: id,
name = obj.str("name") ?: id,
attachment = cap.bool("attachment"),
reasoning = cap.bool("reasoning"),
temperature = cap.bool("temperature"),
toolCall = cap.bool("toolcall"),
free = obj.bool("isFree"),
status = obj.str("status"),
recommendedIndex = obj.num("recommendedIndex"),
variants = variants(obj),
limit = limit?.let {
ModelLimitInfo(
context = it.long("context") ?: 0,
input = it.long("input"),
output = it.long("output") ?: 0,
)
},
)
}
private fun variants(obj: JsonObject): List<String> {
val raw = obj["variants"]?.jsonObject?.keys?.toList() ?: return emptyList()
return raw.sortedWith(compareBy<String> { EFFORT_ORDER[it] ?: Int.MAX_VALUE }.thenBy { it })
}
private fun fetch(path: String): String {
val request = Request.Builder().url("http://localhost:$port$path").get().build()
val request = Request.Builder().url("http://127.0.0.1:$port$path").get().build()
http.newCall(request).execute().use { response ->
val raw = response.body?.string().orEmpty()
if (!response.isSuccessful) throw RuntimeException("HTTP ${response.code}: $raw")
@@ -371,7 +307,3 @@ class KiloBackendWorkspace(
}
private fun encode(value: String) = java.net.URLEncoder.encode(value, Charsets.UTF_8)
private fun JsonObject.str(key: String) = this[key]?.jsonPrimitive?.contentOrNull
private fun JsonObject?.bool(key: String) = this?.get(key)?.jsonPrimitive?.booleanOrNull ?: false
private fun JsonObject.num(key: String) = this[key]?.jsonPrimitive?.doubleOrNull
private fun JsonObject.long(key: String) = this[key]?.jsonPrimitive?.longOrNull
@@ -164,6 +164,22 @@ class KiloBackendAppServiceTest {
assertEquals("alice@test.com", svc.profile!!.profile.email)
}
@Test
fun `set organization sends explicit null body for personal account`() = runBlocking {
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
svc.setOrganization("org_1")
assertEquals("""{"organizationId":"org_1"}""", mock.lastOrganizationSetBody)
svc.setOrganization(null)
assertEquals("""{"organizationId":null}""", mock.lastOrganizationSetBody)
}
@Test
fun `profile 401 does not prevent Ready`() = runBlocking {
mock.profileStatus = 401
@@ -440,6 +456,41 @@ class KiloBackendAppServiceTest {
assertTrue((svc.appState.value as KiloAppState.Ready).data.warnings.isEmpty())
}
// ------ Auth mapping tests ------
@Test
fun `start login maps device auth response`() = runBlocking<Unit> {
// Default authorizeResponse: url=https://auth.kilo.ai/device, code=TEST-1234
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
val auth = svc.startLogin(null)
assertEquals("https://auth.kilo.ai/device", auth.verificationUrl)
assertEquals("TEST-1234", auth.code)
assertEquals(900, auth.expiresIn)
assertNotNull(mock.lastAuthorizeBody)
}
@Test
fun `complete login calls callback and refreshes profile`() = runBlocking<Unit> {
mock.profile = """{"profile":{"email":"alice@test.com","name":"Alice"},"balance":null,"currentOrgId":null}"""
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
val profile = svc.completeLogin(null)
assertNotNull(profile)
assertEquals("alice@test.com", profile.profile.email)
assertNotNull(mock.lastCallbackBody)
}
// ------ Concurrency & lifecycle tests ------
@Test
@@ -519,4 +570,150 @@ class KiloBackendAppServiceTest {
assertIs<KiloAppState.Ready>(svc.appState.value)
}
// ------ Profile DTO mapping tests ------
@Test
fun `ready dto maps profile fields`() = runBlocking {
mock.profile = """{
"profile":{
"email":"alice@test.com",
"name":"Alice",
"organizations":[{"id":"org_1","name":"Acme","role":"ADMIN"}]
},
"balance":{"balance":42.5},
"currentOrgId":"org_1"
}""".trimIndent()
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
val dto = appStateDto(svc.appState.value)
assertEquals("alice@test.com", dto.profile?.email)
assertEquals("Alice", dto.profile?.name)
assertEquals("ADMIN", dto.profile?.organizations?.firstOrNull()?.role)
assertEquals(42.5, dto.profile?.balance?.balance)
assertEquals("org_1", dto.profile?.currentOrgId)
}
@Test
fun `refresh profile updates ready dto profile`() = runBlocking {
mock.profile = """{"profile":{"email":"alice@test.com","name":"Alice"},"balance":null,"currentOrgId":null}"""
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
// Update mock to return different profile
mock.profile = """{"profile":{"email":"alice@test.com","name":"Updated Alice"},"balance":{"balance":99.0},"currentOrgId":null}"""
val fresh = svc.refreshProfile()
assertNotNull(fresh)
assertEquals("Updated Alice", fresh.profile.name)
assertEquals("Updated Alice", appStateDto(svc.appState.value).profile?.name)
assertEquals(99.0, appStateDto(svc.appState.value).profile?.balance?.balance)
}
@Test
fun `logout clears ready profile on success`() = runBlocking {
mock.profile = """{"profile":{"email":"alice@test.com","name":"Alice"},"balance":null,"currentOrgId":null}"""
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
assertNotNull(svc.profile)
mock.authRemoveStatus = 200
val ok = svc.logout()
assertTrue(ok)
assertNull(svc.profile)
assertNull(appStateDto(svc.appState.value).profile)
}
@Test
fun `set organization failure leaves profile unchanged`() = runBlocking {
mock.profile = """{"profile":{"email":"alice@test.com","name":"Alice"},"balance":null,"currentOrgId":null}"""
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
val before = svc.profile
assertNotNull(before)
mock.organizationSetStatus = 500
var thrown = false
try {
svc.setOrganization("org_1")
} catch (_: Exception) {
thrown = true
}
assertTrue(thrown, "setOrganization with 500 should throw")
// Profile should remain unchanged because organization switch failed before refreshProfile
assertEquals(before.profile.email, svc.profile?.profile?.email)
}
@Test
fun `start login failure propagates`() = runBlocking {
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
mock.authorizeStatus = 500
var thrown = false
try {
svc.startLogin(null)
} catch (_: Exception) {
thrown = true
}
assertTrue(thrown, "startLogin with 500 status should throw")
}
@Test
fun `start login without code returns null code but url present`() = runBlocking {
// Instructions without 'code:' — the regex match should return null
mock.authorizeResponse = """{"url":"https://auth.kilo.ai/device","method":"code","instructions":"Open the URL in your browser to sign in"}"""
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
val auth = svc.startLogin(null)
assertNull(auth.code, "code should be null when instructions have no code: prefix")
assertEquals("https://auth.kilo.ai/device", auth.verificationUrl)
}
@Test
fun `complete login callback failure propagates`() = runBlocking {
val svc = create()
svc.connect()
withTimeout(10_000) {
svc.appState.first { it is KiloAppState.Ready }
}
mock.callbackStatus = 500
var thrown = false
try {
svc.completeLogin(null)
} catch (_: Exception) {
thrown = true
}
assertTrue(thrown, "completeLogin with 500 callback status should throw")
}
}

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