mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge branch 'main' into nvidia-nim-origin-header
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add a collapsible sidebar in the Agent Manager. The toggle button sits left of the tab title and the collapsed state persists across reloads and restarts. Starting a new session or worktree automatically reopens the sidebar so the new entry is visible.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep long New Worktree prompts clear of selector controls and show inline model picker options without clipping.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep Agent Manager state hidden from Git before creating local sessions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep VS Code session timeline, token, context, and cost stats in sync after restoring a checkpoint.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Capture aggregate usage telemetry for experimental Morph-backed codebase search.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Show ChatGPT sign-in again when Codex authentication expires.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep continued Agent Manager sessions in the selected worktree instead of moving them to Local.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-ui": patch
|
||||
---
|
||||
|
||||
Show the assistant message copy tooltip above the button so it no longer overlaps the thumbs up/down buttons.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix TUI diff rendering when header-like content lines appear inside a unified diff hunk.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Ensure snapshot installs package a fresh CLI backend so new extension features are available in local VSIX builds.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Hide Task tool subagent sessions from Agent Manager session lists.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Support speech-to-text voice input in Agent Manager inline review comments.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Move worktree file revert diff status checks into the VS Code extension host so Kilo no longer asks the CLI server to run git diff commands through Bun during workspace and Agent Manager reverts.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"@kilocode/sdk": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Support setting an auto-compaction threshold percentage so long sessions can compact before the context window is full.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep inline review voice capture stable in rendered Markdown diffs.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/kilo-docs": patch
|
||||
---
|
||||
|
||||
Harden Mermaid diagram rendering with upstream security fixes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep the New Worktree model picker open when interacting with its search field.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"@kilocode/kilo-ui": patch
|
||||
---
|
||||
|
||||
Keep chat tool cards open or closed when streaming updates remount them.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Use the VS Code-native placeholder color for input fields so placeholder text is consistently distinct from values you have typed (Settings, Agent Manager, prompt input, model search, etc.).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Reset the To-dos tab when reverting a session snapshot.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Add syntax highlighting to shell command output with Shiki, labeled "Command" and "Output" sections, per-section copy buttons, and an "Open in Editor" action that opens the full untruncated output in a VS Code editor tab.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Export full VS Code session transcripts as Markdown files.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix Mermaid diagrams rendering with empty text inside every shape by restoring the `foreignObject` HTML integration point that DOMPurify dropped in 3.1.7.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Restore only the Kilo Code sidebar, Agent Manager, or both on VS Code restart based on which views were open before shutdown.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
"@kilocode/kilo-gateway": minor
|
||||
---
|
||||
|
||||
Support experimental speech-to-text voice input in VS Code prompt fields through Kilo Gateway.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Send recorded voice prompts directly when the prompt send button is pressed.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/kilo-gateway": patch
|
||||
---
|
||||
|
||||
Prevent Kilo Gateway Responses requests from replaying transient provider item IDs when request storage is disabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Support viewing Agent Manager terminal tabs alongside the right diff sidebar, and send review comments to the active terminal.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep configured mode models in sync when switching modes in VS Code.
|
||||
@@ -0,0 +1,167 @@
|
||||
# kilocode_change - new file
|
||||
name: publish-jetbrains
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "jetbrains/*"
|
||||
|
||||
concurrency:
|
||||
group: publish-jetbrains-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y patchelf zip
|
||||
|
||||
- name: Validate version tag
|
||||
id: version
|
||||
run: |
|
||||
tag="$GITHUB_REF_NAME"
|
||||
if [[ "$tag" != jetbrains/v* ]]; then
|
||||
echo "Unsupported tag '$tag'. Expected jetbrains/v<version>." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${tag#jetbrains/v}"
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "kind=rc"
|
||||
echo "marketplace_channel=eap"
|
||||
echo "cli_channel=rc"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "kind=stable"
|
||||
echo "marketplace_channel=default"
|
||||
echo "cli_channel=latest"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "Stable JetBrains Marketplace publishing is implemented but intentionally disabled; use an rc tag such as jetbrains/v7.0.1-rc.1." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Unsupported JetBrains plugin version '$version'. Expected jetbrains/vx.y.z-rc.n or jetbrains/vx.y.z." >&2
|
||||
exit 1
|
||||
|
||||
- name: Validate publishing secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in JETBRAINS_MARKETPLACE_TOKEN JETBRAINS_CERTIFICATE_CHAIN JETBRAINS_PRIVATE_KEY JETBRAINS_PRIVATE_KEY_PASSWORD; do
|
||||
if [[ -z "${!name}" ]]; then
|
||||
echo "Missing required secret: $name" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
exit "$missing"
|
||||
env:
|
||||
JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
|
||||
JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }}
|
||||
JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }}
|
||||
JETBRAINS_PRIVATE_KEY_PASSWORD: ${{ secrets.JETBRAINS_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Prepare CLI resources
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: bun script/build.ts --production --prepare-cli
|
||||
env:
|
||||
KILO_VERSION: ${{ steps.version.outputs.version }}
|
||||
KILO_CHANNEL: ${{ steps.version.outputs.cli_channel }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
- name: Verify plugin
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew verifyPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
CHANNEL: ${{ steps.version.outputs.marketplace_channel }}
|
||||
|
||||
- name: Publish to JetBrains Marketplace
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew publishPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
CHANNEL: ${{ steps.version.outputs.marketplace_channel }}
|
||||
JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
|
||||
JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }}
|
||||
JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }}
|
||||
JETBRAINS_PRIVATE_KEY_PASSWORD: ${{ secrets.JETBRAINS_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Resolve plugin archive
|
||||
id: archive
|
||||
run: |
|
||||
mapfile -t signed < <(compgen -G "packages/kilo-jetbrains/build/distributions/*-signed.zip")
|
||||
if [[ "${#signed[@]}" -eq 1 ]]; then
|
||||
echo "path=${signed[0]}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${#signed[@]}" -gt 1 ]]; then
|
||||
echo "Expected exactly one signed JetBrains plugin ZIP, found ${#signed[@]}." >&2
|
||||
printf '%s\n' "${signed[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t files < <(compgen -G "packages/kilo-jetbrains/build/distributions/*.zip")
|
||||
if [[ "${#files[@]}" -ne 1 ]]; then
|
||||
echo "Expected exactly one JetBrains plugin ZIP, found ${#files[@]}." >&2
|
||||
printf '%s\n' "${files[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "path=${files[0]}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
run: |
|
||||
tag="$GITHUB_REF_NAME"
|
||||
title="JetBrains $VERSION"
|
||||
notes="JetBrains plugin $VERSION."
|
||||
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release upload "$tag" "$ARCHIVE" --clobber --repo "$GITHUB_REPOSITORY"
|
||||
exit 0
|
||||
fi
|
||||
gh release create "$tag" "$ARCHIVE" --title "$title" --notes "$notes" --prerelease --repo "$GITHUB_REPOSITORY"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
ARCHIVE: ${{ steps.archive.outputs.path }}
|
||||
|
||||
- name: Upload workflow artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kilo-jetbrains-${{ steps.version.outputs.version }}
|
||||
path: packages/kilo-jetbrains/build/distributions/*.zip
|
||||
if-no-files-found: ignore
|
||||
@@ -41,11 +41,21 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6 # kilocode_change
|
||||
id: setup-node
|
||||
continue-on-error: ${{ runner.os == 'Windows' }}
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Retry Setup Node on Windows
|
||||
if: runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
# kilocode_change end
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
|
||||
@@ -102,7 +102,9 @@ kilo run --auto "run tests and fix any failures"
|
||||
We welcome contributions from developers, writers, and enthusiasts!
|
||||
To get started, please read our [Contributing Guide](/CONTRIBUTING.md). It includes details on setting up your environment, coding standards, types of contribution and how to submit pull requests.
|
||||
|
||||
See [RELEASING.md](RELEASING.md) for the release process.
|
||||
See [RELEASING.md](RELEASING.md) for the VS Code extension and CLI release process.
|
||||
|
||||
See [packages/kilo-jetbrains/RELEASING.md](packages/kilo-jetbrains/RELEASING.md) for the JetBrains plugin release process.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -77,7 +77,7 @@
|
||||
"@vscode/codicons": "^0.0.44",
|
||||
"@xyflow/react": "12.10.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mermaid": "11.12.3",
|
||||
"mermaid": "11.15.0",
|
||||
"next": "^16.1.5",
|
||||
"posthog-js": "^1.335.3",
|
||||
"prismjs": "^1.30.0",
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"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.2.52",
|
||||
"version": "7.3.0",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/kilo-indexing": {
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.972.31",
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"posthog-node": "4.4.0",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -229,7 +229,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
@@ -292,7 +292,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -450,7 +450,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -475,7 +475,7 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"semver": "^7.6.3",
|
||||
},
|
||||
@@ -486,7 +486,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -501,7 +501,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -524,7 +524,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -546,7 +546,7 @@
|
||||
"marked": "catalog:",
|
||||
"marked-katex-extension": "5.1.6",
|
||||
"marked-shiki": "catalog:",
|
||||
"mermaid": "11.14.0",
|
||||
"mermaid": "11.15.0",
|
||||
"morphdom": "2.7.8",
|
||||
"motion": "12.34.5",
|
||||
"motion-dom": "12.34.3",
|
||||
@@ -968,15 +968,7 @@
|
||||
|
||||
"@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="],
|
||||
|
||||
"@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@12.0.0", "", { "dependencies": { "@chevrotain/gast": "12.0.0", "@chevrotain/types": "12.0.0" } }, "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg=="],
|
||||
|
||||
"@chevrotain/gast": ["@chevrotain/gast@12.0.0", "", { "dependencies": { "@chevrotain/types": "12.0.0" } }, "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ=="],
|
||||
|
||||
"@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@12.0.0", "", {}, "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA=="],
|
||||
|
||||
"@chevrotain/types": ["@chevrotain/types@12.0.0", "", {}, "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA=="],
|
||||
|
||||
"@chevrotain/utils": ["@chevrotain/utils@12.0.0", "", {}, "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA=="],
|
||||
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
|
||||
|
||||
"@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="],
|
||||
|
||||
@@ -1348,7 +1340,7 @@
|
||||
|
||||
"@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
|
||||
|
||||
"@mermaid-js/parser": ["@mermaid-js/parser@1.1.0", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw=="],
|
||||
"@mermaid-js/parser": ["@mermaid-js/parser@1.1.1", "", { "dependencies": { "@chevrotain/types": "~11.1.1" } }, "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw=="],
|
||||
|
||||
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
|
||||
|
||||
@@ -2554,10 +2546,6 @@
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
|
||||
"chevrotain": ["chevrotain@12.0.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "12.0.0", "@chevrotain/gast": "12.0.0", "@chevrotain/regexp-to-ast": "12.0.0", "@chevrotain/types": "12.0.0", "@chevrotain/utils": "12.0.0" } }, "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ=="],
|
||||
|
||||
"chevrotain-allstar": ["chevrotain-allstar@0.4.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^12.0.0" } }, "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
@@ -2742,7 +2730,7 @@
|
||||
|
||||
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
|
||||
|
||||
"dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="],
|
||||
"dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
@@ -2876,6 +2864,8 @@
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"esbuild-plugin-solid": ["esbuild-plugin-solid@0.6.0", "", { "dependencies": { "@babel/core": "^7.20.12", "@babel/preset-typescript": "^7.18.6", "babel-preset-solid": "^1.6.9" }, "peerDependencies": { "esbuild": ">=0.20", "solid-js": ">= 1.0" } }, "sha512-V1FvDALwLDX6K0XNYM9CMRAnMzA0+Ecu55qBUT9q/eAJh1KIDsTMFoOzMSgyHqbOfvrVfO3Mws3z7TW2GVnIZA=="],
|
||||
@@ -3360,8 +3350,6 @@
|
||||
|
||||
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
|
||||
|
||||
"langium": ["langium@4.2.2", "", { "dependencies": { "@chevrotain/regexp-to-ast": "~12.0.0", "chevrotain": "~12.0.0", "chevrotain-allstar": "~0.4.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ=="],
|
||||
|
||||
"layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="],
|
||||
|
||||
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
|
||||
@@ -3492,7 +3480,7 @@
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"mermaid": ["mermaid@11.12.3", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ=="],
|
||||
"mermaid": ["mermaid@11.15.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "es-toolkit": "^1.45.1", "katex": "^0.16.25", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw=="],
|
||||
|
||||
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
|
||||
|
||||
@@ -4420,16 +4408,8 @@
|
||||
|
||||
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
|
||||
|
||||
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
|
||||
|
||||
"vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
|
||||
|
||||
"vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="],
|
||||
|
||||
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||
|
||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||
|
||||
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
@@ -4736,8 +4716,6 @@
|
||||
|
||||
"@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||
|
||||
"@opencode-ai/ui/mermaid": ["mermaid@11.14.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g=="],
|
||||
|
||||
"@opencode-ai/ui/tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],
|
||||
@@ -5014,8 +4992,6 @@
|
||||
|
||||
"mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
|
||||
|
||||
"mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
@@ -5184,8 +5160,6 @@
|
||||
|
||||
"vitest/why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||
|
||||
"whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -5310,12 +5284,6 @@
|
||||
|
||||
"@opencode-ai/ui/@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=="],
|
||||
|
||||
"@opencode-ai/ui/mermaid/dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
|
||||
|
||||
"@opencode-ai/ui/mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
|
||||
|
||||
"@opencode-ai/ui/mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[install]
|
||||
exact = true
|
||||
minimumReleaseAge = 410520 # seconds (~4.75 days / ~114 hours)
|
||||
minimumReleaseAgeExcludes = ["mermaid", "@mermaid-js/parser"]
|
||||
|
||||
[test]
|
||||
root = "./do-not-run-tests-from-root"
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-n8qDAnmhHLO3PICGFAL6UzPi6k+Dwtl9Wg87PKqa/vY=",
|
||||
"aarch64-linux": "sha256-XBCHg6BYL1J3M5JCUZda9e8ZKi8EEpI2hez6w3ICOYs=",
|
||||
"aarch64-darwin": "sha256-3K35v/UghrM8CSpWvbvJVLeoF94MALWVA7F5B++Po00=",
|
||||
"x86_64-darwin": "sha256-aTjNOPvGygJ34A1Q5+qziyyuSFa0w6WMUjUcsXSLtBU="
|
||||
"x86_64-linux": "sha256-NZqcbv0MX1WV/BkAT1poERcDEFiA4s1Gsf7W4p5wjlk=",
|
||||
"aarch64-linux": "sha256-WSl5114O35I9Fae7+BbYXWznKSApPXN/Ripjqr6NO5U=",
|
||||
"aarch64-darwin": "sha256-VemzuMnNbZo7vgHS5UiHVFGPVy9VL3gVwm+cw6ij1cg=",
|
||||
"x86_64-darwin": "sha256-tg2Zt8qE5EZWCVNLxKzXtaygvzAkO0IFysMfFhIfZBs="
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -135,6 +135,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.2.52",
|
||||
"version": "7.3.0",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.2.52"
|
||||
version = "7.3.0"
|
||||
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.2.52/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.0/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.2.52/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.0/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.52/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.0/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.2.52/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.0/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.2.52/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.0/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@vscode/codicons": "^0.0.44",
|
||||
"@xyflow/react": "12.10.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mermaid": "11.12.3",
|
||||
"mermaid": "11.15.0",
|
||||
"next": "^16.1.5",
|
||||
"posthog-js": "^1.335.3",
|
||||
"prismjs": "^1.30.0",
|
||||
|
||||
@@ -76,6 +76,12 @@ Find the Kilo Code icon ({% kiloCodeIcon /%}) in VS Code's Primary Side Bar. Cli
|
||||
|
||||
The extension automatically passes context from your editor, including your open tabs and active file. You can type `@` in the chat input to get file and terminal autocomplete suggestions — use `@filename` to attach a file or `@terminal` to include your active terminal output. You can also mention file paths naturally in your message (e.g., "update src/utils.ts to add a helper function"). The agent can also discover files on its own using its built-in tools.
|
||||
|
||||
**Exporting local transcripts:**
|
||||
|
||||
Run `/export` in chat, or open a local session's **History** context menu and choose **Export session transcript**. The save dialog lets you choose the Markdown (`.md`) destination.
|
||||
|
||||
Kilo builds the export from the complete local session history, not only the messages currently loaded in the chat view.
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="CLI" %}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ Key features include:
|
||||
- **[Skills](/docs/customize/skills)** — Load specialized domain knowledge from SKILL.md files
|
||||
- **[Custom Subagents](/docs/customize/custom-subagents)** — Define specialized sub-agents for the `task` tool
|
||||
- **Open in Tab** — Pop the chat out into a full editor tab
|
||||
- **Transcript export:** Save complete local session transcripts as Markdown files
|
||||
- **Sub-Agent Viewer** — Read-only panels for viewing child agent sessions
|
||||
- **Legacy Migration** — Automatic migration wizard for VSCode extension settings
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -65,6 +65,7 @@ export const MODELS_FETCH_TIMEOUT_MS = 10 * 1000
|
||||
*/
|
||||
export const HEADER_ORGANIZATIONID = "X-KILOCODE-ORGANIZATIONID"
|
||||
export const HEADER_TASKID = "X-KILOCODE-TASKID"
|
||||
export const HEADER_PARENT_TASKID = "X-KILOCODE-PARENT-TASKID"
|
||||
export const HEADER_PROJECTID = "X-KILOCODE-PROJECTID"
|
||||
export const HEADER_TESTER = "X-KILOCODE-TESTER"
|
||||
export const HEADER_EDITORNAME = "X-KILOCODE-EDITORNAME"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
HEADER_ORGANIZATIONID,
|
||||
HEADER_TASKID,
|
||||
HEADER_PARENT_TASKID,
|
||||
HEADER_PROJECTID,
|
||||
HEADER_TESTER,
|
||||
HEADER_EDITORNAME,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
*/
|
||||
export const X_KILOCODE_ORGANIZATIONID = HEADER_ORGANIZATIONID
|
||||
export const X_KILOCODE_TASKID = HEADER_TASKID
|
||||
export const X_KILOCODE_PARENT_TASKID = HEADER_PARENT_TASKID
|
||||
export const X_KILOCODE_PROJECTID = HEADER_PROJECTID
|
||||
export const X_KILOCODE_TESTER = HEADER_TESTER
|
||||
export const X_KILOCODE_EDITORNAME = HEADER_EDITORNAME
|
||||
|
||||
@@ -100,6 +100,7 @@ export {
|
||||
MODELS_FETCH_TIMEOUT_MS,
|
||||
HEADER_ORGANIZATIONID,
|
||||
HEADER_TASKID,
|
||||
HEADER_PARENT_TASKID,
|
||||
HEADER_PROJECTID,
|
||||
HEADER_TESTER,
|
||||
HEADER_EDITORNAME,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getApiKey } from "./auth/token.js"
|
||||
import { buildKiloHeaders, getDefaultHeaders } from "./headers.js"
|
||||
import { ANONYMOUS_API_KEY } from "./api/constants.js"
|
||||
import { resolveKiloOpenRouterBaseUrl } from "./api/url.js"
|
||||
import { buildRequestHeaders } from "./provider.js"
|
||||
|
||||
/**
|
||||
* Debug version of createKilo with extensive logging
|
||||
@@ -40,12 +41,7 @@ export function createKiloDebug(options: KiloProviderOptions = {}): SDK {
|
||||
console.log(" - URL:", String(input))
|
||||
console.log(" - Method:", init?.method || "GET")
|
||||
|
||||
const headers = new Headers(init?.headers)
|
||||
|
||||
// Add custom headers
|
||||
Object.entries(customHeaders).forEach(([key, value]) => {
|
||||
headers.set(key, value)
|
||||
})
|
||||
const headers = buildRequestHeaders(customHeaders, init?.headers)
|
||||
|
||||
// Add authorization if API key exists
|
||||
if (apiKey) {
|
||||
|
||||
@@ -10,6 +10,14 @@ import { ANONYMOUS_API_KEY } from "./api/constants.js"
|
||||
import { resolveKiloOpenRouterBaseUrl } from "./api/url.js"
|
||||
import { sanitizeResponsesBody } from "./responses.js"
|
||||
|
||||
export function buildRequestHeaders(defaultHeaders: Record<string, string>, requestHeaders?: HeadersInit): Headers {
|
||||
const headers = new Headers(defaultHeaders)
|
||||
new Headers(requestHeaders).forEach((value, key) => {
|
||||
headers.set(key, value)
|
||||
})
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a KiloCode provider instance
|
||||
*
|
||||
@@ -45,14 +53,9 @@ export function createKilo(options: KiloProviderOptions = {}): KiloProvider {
|
||||
// Create custom fetch wrapper to add dynamic headers
|
||||
const originalFetch = options.fetch ?? fetch
|
||||
const wrappedFetch = async (input: string | URL | Request, init?: RequestInit) => {
|
||||
const headers = new Headers(init?.headers)
|
||||
const headers = buildRequestHeaders(customHeaders, init?.headers)
|
||||
const body = sanitizeResponsesBody(input, init?.body)
|
||||
|
||||
// Add custom headers
|
||||
Object.entries(customHeaders).forEach(([key, value]) => {
|
||||
headers.set(key, value)
|
||||
})
|
||||
|
||||
// Add authorization if API key exists
|
||||
if (apiKey) {
|
||||
headers.set("Authorization", `Bearer ${apiKey}`)
|
||||
|
||||
@@ -429,6 +429,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
format: z.string(),
|
||||
}),
|
||||
language: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { buildRequestHeaders } from "../src/provider"
|
||||
|
||||
describe("Kilo provider request headers", () => {
|
||||
test("request headers override provider defaults", () => {
|
||||
const headers = buildRequestHeaders(
|
||||
{
|
||||
"content-type": "application/json",
|
||||
"x-kilocode-feature": "vscode-extension",
|
||||
"x-default-only": "kept",
|
||||
},
|
||||
{
|
||||
"x-kilocode-feature": "agent-manager",
|
||||
"x-request-only": "kept-too",
|
||||
},
|
||||
)
|
||||
|
||||
expect(headers.get("content-type")).toBe("application/json")
|
||||
expect(headers.get("x-kilocode-feature")).toBe("agent-manager")
|
||||
expect(headers.get("x-default-only")).toBe("kept")
|
||||
expect(headers.get("x-request-only")).toBe("kept-too")
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Kilo-specific i18n translations and overrides",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Standalone indexing engine and host helpers for Kilo Code",
|
||||
|
||||
@@ -52,6 +52,12 @@ The built plugin archive is at `build/distributions/kilo.jetbrains-<version>.zip
|
||||
|
||||
---
|
||||
|
||||
## Releasing
|
||||
|
||||
See [RELEASING.md](RELEASING.md) for the full release process, including how to tag and push an RC, where to watch workflow progress, and how to install RC builds via the custom plugin repository.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# JetBrains Release Todo
|
||||
|
||||
## One-Time Marketplace Setup
|
||||
|
||||
- Confirm `ai.kilocode.jetbrains` is the final permanent Marketplace plugin ID before the first upload.
|
||||
- Build a production ZIP locally or in CI for the first Marketplace version.
|
||||
- Manually upload the first plugin version in JetBrains Marketplace if the plugin has not been published before.
|
||||
- Confirm the plugin is owned by the correct Kilo vendor or organization.
|
||||
- Confirm the `eap` custom channel exists or is accepted on the first RC upload.
|
||||
- Confirm Marketplace signing requirements before publishing RC builds.
|
||||
|
||||
## One-Time GitHub Setup
|
||||
|
||||
- Create a JetBrains Marketplace permanent token from Marketplace `My Tokens`.
|
||||
- Add `JETBRAINS_MARKETPLACE_TOKEN` to GitHub Actions secrets or the protected environment.
|
||||
- Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases from `jetbrains/v*` tags.
|
||||
- Optionally create a protected `jetbrains-marketplace` GitHub Environment with required reviewers.
|
||||
- If using an environment, move the Marketplace and signing secrets there and set the workflow job environment.
|
||||
|
||||
## One-Time Signing Certificate Setup
|
||||
|
||||
- Generate or locate the JetBrains plugin signing private key and certificate chain.
|
||||
- Add `JETBRAINS_CERTIFICATE_CHAIN` with the full certificate chain PEM content.
|
||||
- Add `JETBRAINS_PRIVATE_KEY` with the private key PEM content.
|
||||
- Add `JETBRAINS_PRIVATE_KEY_PASSWORD` with the private key password.
|
||||
- Keep PEM files out of git, local logs, and workflow output.
|
||||
- If GitHub secret input mangles multiline PEM values, store base64-encoded values and verify the Gradle signing task decodes them correctly.
|
||||
|
||||
## Per-RC Release
|
||||
|
||||
- Choose an RC version in the form `x.y.z-rc.n`.
|
||||
- Push tag `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`.
|
||||
- Watch the `publish-jetbrains` workflow.
|
||||
- Download and retain the workflow artifact if needed.
|
||||
- Confirm the update appears on the JetBrains Marketplace `eap` channel.
|
||||
- Confirm the GitHub Release for the `jetbrains/vx.y.z-rc.n` tag exists and contains the JetBrains plugin ZIP asset.
|
||||
- Share `https://plugins.jetbrains.com/plugins/eap/list` with testers.
|
||||
|
||||
## Stable Release Guard
|
||||
|
||||
- Stable tags like `jetbrains/vx.y.z` are intentionally rejected for now.
|
||||
- Before enabling stable releases, remove the workflow stable guard.
|
||||
- Verify `kilo.channel=default` publishes to the default Marketplace channel.
|
||||
- Update this checklist before stable releases are enabled.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Releasing the JetBrains Plugin
|
||||
|
||||
## RC releases (currently the only supported flow)
|
||||
|
||||
Stable release tags (`jetbrains/vx.y.z`) are recognized by the workflow but intentionally rejected. Only RC tags are accepted right now.
|
||||
|
||||
### 1. Create and push a tag
|
||||
|
||||
Tag format: `jetbrains/v<major>.<minor>.<patch>-rc.<n>`
|
||||
|
||||
```
|
||||
git tag jetbrains/v7.0.1-rc.1
|
||||
git push origin jetbrains/v7.0.1-rc.1
|
||||
```
|
||||
|
||||
### 2. Watch the workflow
|
||||
|
||||
The `publish-jetbrains` workflow starts automatically on tag push. Follow progress at:
|
||||
|
||||
[https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml)
|
||||
|
||||
The workflow:
|
||||
1. Validates the tag format and required secrets.
|
||||
2. Downloads CLI binaries for all 6 platforms from the matching GitHub Release.
|
||||
3. Verifies and signs the plugin with `./gradlew verifyPlugin publishPlugin -Pproduction=true`.
|
||||
4. Publishes the signed ZIP to the JetBrains Marketplace `eap` channel.
|
||||
5. Uploads the signed ZIP to a GitHub prerelease for the tag.
|
||||
|
||||
### 3. Verify on the Marketplace
|
||||
|
||||
Once the workflow succeeds, the new version should appear in the plugin's version list:
|
||||
|
||||
[https://plugins.jetbrains.com/plugin/28350-kilo-code/edit/versions](https://plugins.jetbrains.com/plugin/28350-kilo-code/edit/versions)
|
||||
|
||||
---
|
||||
|
||||
## Installing RC builds via the custom plugin repository
|
||||
|
||||
RC builds are published to the `eap` channel, not the default channel. To get them in IntelliJ IDEA:
|
||||
|
||||
1. Open **Settings > Plugins**.
|
||||
2. Click the gear icon and choose **Manage Plugin Repositories**.
|
||||
3. Add the following URL:
|
||||
|
||||
```
|
||||
https://plugins.jetbrains.com/plugins/list?channel=eap&pluginId=28350
|
||||
```
|
||||
|
||||
4. Search for **Kilo Code** in the Marketplace tab — the latest RC version will appear and update automatically.
|
||||
|
||||
---
|
||||
|
||||
## Required GitHub Actions secrets
|
||||
|
||||
| Secret | Purpose |
|
||||
|---|---|
|
||||
| `JETBRAINS_MARKETPLACE_TOKEN` | Marketplace API token for publishing |
|
||||
| `JETBRAINS_CERTIFICATE_CHAIN` | PEM certificate chain for plugin signing |
|
||||
| `JETBRAINS_PRIVATE_KEY` | PEM private key for plugin signing |
|
||||
| `JETBRAINS_PRIVATE_KEY_PASSWORD` | Password for the private key |
|
||||
|
||||
Before the first publish, complete `RELEASE_TODO.md` to set up these secrets and the Marketplace plugin entry.
|
||||
@@ -4,7 +4,32 @@ import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask
|
||||
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware.SplitModeTarget
|
||||
|
||||
group = "ai.kilocode.jetbrains"
|
||||
version = "7.0.1"
|
||||
|
||||
fun checked(value: String): String {
|
||||
if (value == "0.0.0-dev") return value
|
||||
require(Regex("^[0-9]+\\.[0-9]+\\.[0-9]+(-rc\\.[0-9]+)?$").matches(value)) {
|
||||
"Invalid JetBrains plugin version: $value"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fun gitTag(): String? {
|
||||
val text = providers.exec {
|
||||
commandLine("git", "tag", "--points-at", "HEAD")
|
||||
}.standardOutput.asText.get()
|
||||
return text.lineSequence().map { it.trim() }.firstOrNull { it.startsWith("jetbrains/v") }
|
||||
}
|
||||
|
||||
val release = providers.gradleProperty("production").map { it.toBoolean() }.orElse(false).get()
|
||||
val ver = if (release) checked(
|
||||
gitTag()?.removePrefix("jetbrains/v")
|
||||
?: error("Missing JetBrains plugin version. Publish builds must run from a jetbrains/v<version> tag."),
|
||||
) else checked(gitTag()?.removePrefix("jetbrains/v") ?: "0.0.0-dev")
|
||||
|
||||
val notes = providers.gradleProperty("kilo.changeNotes").orElse("Release candidate build.")
|
||||
val channel = providers.gradleProperty("kilo.channel").map { it.trim() }.orElse("default")
|
||||
|
||||
version = ver
|
||||
|
||||
plugins {
|
||||
application
|
||||
@@ -59,9 +84,39 @@ intellijPlatform {
|
||||
splitMode = true
|
||||
splitModeTarget = SplitModeTarget.BOTH
|
||||
|
||||
pluginConfiguration {
|
||||
id = "ai.kilocode.jetbrains"
|
||||
name = "Kilo Code"
|
||||
version = provider { ver }
|
||||
changeNotes = notes
|
||||
|
||||
ideaVersion {
|
||||
untilBuild = provider { null }
|
||||
}
|
||||
|
||||
vendor {
|
||||
name = "Kilo Code"
|
||||
url = "https://kilo.ai"
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
token = providers.environmentVariable("JETBRAINS_MARKETPLACE_TOKEN")
|
||||
channels = channel.map { value ->
|
||||
if (value.isBlank() || value == "default") return@map listOf("default")
|
||||
listOf(value)
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
certificateChain = providers.environmentVariable("JETBRAINS_CERTIFICATE_CHAIN")
|
||||
privateKey = providers.environmentVariable("JETBRAINS_PRIVATE_KEY")
|
||||
password = providers.environmentVariable("JETBRAINS_PRIVATE_KEY_PASSWORD")
|
||||
}
|
||||
|
||||
pluginVerification {
|
||||
ides {
|
||||
create(IntelliJPlatformType.IntellijIdeaCommunity, libs.versions.intellij.platform)
|
||||
create(IntelliJPlatformType.IntellijIdea, libs.versions.intellij.platform)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ import ai.kilocode.client.app.KiloSessionService
|
||||
import ai.kilocode.client.app.Workspace
|
||||
import ai.kilocode.client.session.model.SessionModelEvent
|
||||
import ai.kilocode.client.session.model.SessionState
|
||||
import ai.kilocode.client.session.scroll.SessionScroll
|
||||
import ai.kilocode.client.session.ui.ConnectionPanel
|
||||
import ai.kilocode.client.session.ui.EmptySessionPanel
|
||||
import ai.kilocode.client.session.ui.LoadingPanel
|
||||
|
||||
+10
-12
@@ -5,6 +5,8 @@ import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.session.ui.LoadingPanel
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.HoverIcon
|
||||
import ai.kilocode.client.ui.iconButton
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.ui.LafManagerListener
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
@@ -12,11 +14,7 @@ import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DataProvider
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.openapi.actionSystem.impl.ActionButton
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.PopupHandler
|
||||
@@ -180,12 +178,12 @@ class HistoryPanel(
|
||||
|
||||
private fun back(): BorderLayoutPanel {
|
||||
val label = KiloBundle.message("history.back")
|
||||
val action = object : AnAction(label, null, AllIcons.Actions.Back) {
|
||||
override fun actionPerformed(e: AnActionEvent) = nav()
|
||||
}
|
||||
val presentation = Presentation(label).apply { icon = AllIcons.Actions.Back }
|
||||
val btn = ActionButton(action, presentation, ActionPlaces.TOOLBAR, JBUI.size(16)).apply {
|
||||
val btn = HoverIcon().apply {
|
||||
icon = AllIcons.Actions.Back
|
||||
toolTipText = label
|
||||
accessibleContext.accessibleName = label
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
addActionListener { nav() }
|
||||
}
|
||||
return BorderLayoutPanel().apply {
|
||||
add(btn, BorderLayout.WEST)
|
||||
@@ -379,17 +377,17 @@ class HistoryPanel(
|
||||
|
||||
internal fun backText(): String? {
|
||||
val view = activeInfo().foreSideComponent ?: return null
|
||||
return UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.presentation?.text
|
||||
return UIUtil.uiTraverser(view).filter(HoverIcon::class.java).firstOrNull()?.toolTipText
|
||||
}
|
||||
|
||||
internal fun backCursor(): Int? {
|
||||
val view = activeInfo().foreSideComponent ?: return null
|
||||
return UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.cursor?.type
|
||||
return UIUtil.uiTraverser(view).filter(HoverIcon::class.java).firstOrNull()?.cursor?.type
|
||||
}
|
||||
|
||||
internal fun clickBack() {
|
||||
val view = activeInfo().foreSideComponent ?: return
|
||||
UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.click()
|
||||
UIUtil.uiTraverser(view).filter(HoverIcon::class.java).firstOrNull()?.doClick()
|
||||
}
|
||||
|
||||
internal fun clickDelete() {
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package ai.kilocode.client.session.scroll
|
||||
|
||||
import ai.kilocode.client.ui.colorizeIfPossible
|
||||
import com.intellij.openapi.util.IconLoader
|
||||
import com.intellij.util.ui.JBUI
|
||||
import javax.swing.Icon
|
||||
|
||||
internal object ScrollButtonIcon {
|
||||
private val icon = IconLoader.getIcon("/icons/scroll-bottom.svg", ScrollButtonIcon::class.java)
|
||||
|
||||
fun create(): Icon = icon.colorizeIfPossible(
|
||||
fillColor = JBUI.CurrentTheme.Button.defaultButtonColorStart(),
|
||||
borderColor = JBUI.CurrentTheme.Button.defaultButtonForeground(),
|
||||
fillId = "ScrollButton.Background",
|
||||
strokeId = "ScrollButton.Foreground",
|
||||
)
|
||||
}
|
||||
+3
-44
@@ -1,4 +1,4 @@
|
||||
package ai.kilocode.client.session
|
||||
package ai.kilocode.client.session.scroll
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.ui.SessionMessageListPanel
|
||||
@@ -7,27 +7,18 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.IconLoader
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.icons.CachedImageIcon
|
||||
import com.intellij.ui.svg.SvgAttributePatcher
|
||||
import com.intellij.util.SVGLoader
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.Color
|
||||
import java.awt.Cursor
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.JScrollBar
|
||||
|
||||
private const val ICON_DIGEST = 0x5c011b0bb17L
|
||||
private const val OPAQUE_ALPHA = 255
|
||||
|
||||
internal class SessionScroll(
|
||||
private val root: SessionRootPanel,
|
||||
private val host: JPanel,
|
||||
@@ -35,7 +26,6 @@ internal class SessionScroll(
|
||||
body: JPanel,
|
||||
) {
|
||||
companion object {
|
||||
private val ICON = IconLoader.getIcon("/icons/scroll-bottom.svg", SessionScroll::class.java)
|
||||
private const val THRESHOLD = 32
|
||||
private const val OPEN_PASSES = 12
|
||||
}
|
||||
@@ -58,7 +48,7 @@ internal class SessionScroll(
|
||||
private var seq = 0
|
||||
|
||||
init {
|
||||
jump = JBLabel(patchedIcon(ICON)).apply {
|
||||
jump = JBLabel(ScrollButtonIcon.create()).apply {
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
toolTipText = KiloBundle.message("session.scroll.bottom")
|
||||
isVisible = false
|
||||
@@ -130,7 +120,7 @@ internal class SessionScroll(
|
||||
|
||||
fun applyStyle(style: SessionEditorStyle) {
|
||||
this.style = style
|
||||
jump.icon = patchedIcon(ICON)
|
||||
jump.icon = ScrollButtonIcon.create()
|
||||
messages.applyStyle(style)
|
||||
val view = component.viewport.view
|
||||
if (view !== messages) (view as? SessionEditorStyleTarget)?.applyStyle(style)
|
||||
@@ -231,34 +221,3 @@ internal class SessionScroll(
|
||||
root.overlay.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
private fun patchedIcon(icon: Icon): Icon {
|
||||
val cached = icon as? CachedImageIcon ?: return icon
|
||||
return cached.createWithPatcher(object : SVGLoader.SvgElementColorPatcherProvider, SvgAttributePatcher {
|
||||
override fun digest(): LongArray {
|
||||
val bg = JBUI.CurrentTheme.Button.defaultButtonColorStart().rgb.toLong()
|
||||
val fg = JBUI.CurrentTheme.Button.defaultButtonForeground().rgb.toLong()
|
||||
return longArrayOf(bg, fg, ICON_DIGEST)
|
||||
}
|
||||
|
||||
override fun attributeForPath(path: String) = this
|
||||
|
||||
override fun patchColors(attributes: MutableMap<String, String>) {
|
||||
when (attributes["id"]) {
|
||||
"ScrollButton.Background" ->
|
||||
set(attributes, "fill", JBUI.CurrentTheme.Button.defaultButtonColorStart())
|
||||
|
||||
"ScrollButton.Foreground" ->
|
||||
set(attributes, "stroke", JBUI.CurrentTheme.Button.defaultButtonForeground())
|
||||
}
|
||||
}
|
||||
|
||||
private fun set(attributes: MutableMap<String, String>, key: String, color: Color) {
|
||||
if (!attributes.containsKey(key) || attributes[key] == "none") return
|
||||
attributes[key] = "rgb(${color.red},${color.green},${color.blue})"
|
||||
if (color.alpha != OPAQUE_ALPHA) {
|
||||
attributes["$key-opacity"] = "${color.alpha / OPAQUE_ALPHA.toFloat()}"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package ai.kilocode.client.session.ui
|
||||
|
||||
import com.intellij.openapi.ui.popup.util.PopupUtil
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
import com.intellij.ui.NewUI
|
||||
import com.intellij.ui.popup.list.SelectablePanel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
@@ -23,7 +23,7 @@ internal class PickerRow : SelectablePanel() {
|
||||
fun update(list: JList<*>, selected: Boolean, focused: Boolean) {
|
||||
background = list.background
|
||||
selectionColor = if (selected) UIUtil.getListBackground(true, focused) else null
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
if (NewUI.isEnabled()) {
|
||||
PopupUtil.configListRendererFlexibleHeight(this)
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ import com.intellij.openapi.ui.popup.PopupShowOptions
|
||||
import com.intellij.openapi.ui.popup.util.PopupUtil
|
||||
import com.intellij.ui.CollectionListModel
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.NewUI
|
||||
import com.intellij.ui.ListUtil
|
||||
import com.intellij.ui.SearchTextField
|
||||
import com.intellij.ui.ScrollPaneFactory
|
||||
@@ -38,7 +38,7 @@ import javax.swing.SwingUtilities
|
||||
import javax.swing.event.DocumentEvent
|
||||
|
||||
private val popupBackground: Color
|
||||
get() = if (ExperimentalUI.isNewUI()) JBUI.CurrentTheme.Popup.BACKGROUND else UIUtil.getListBackground()
|
||||
get() = if (NewUI.isEnabled()) JBUI.CurrentTheme.Popup.BACKGROUND else UIUtil.getListBackground()
|
||||
|
||||
private const val MODEL_PICKER_MIN_WIDTH = 420
|
||||
private const val MODEL_PICKER_MAX_WIDTH = 760
|
||||
|
||||
+6
-8
@@ -5,8 +5,8 @@ import ai.kilocode.client.session.ui.PickerRow
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ui.CollectionListModel
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
import com.intellij.ui.GroupHeaderSeparator
|
||||
import com.intellij.ui.NewUI
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
@@ -23,7 +23,7 @@ import java.awt.Graphics2D
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
import java.awt.RenderingHints
|
||||
import java.awt.Toolkit
|
||||
import java.awt.font.FontRenderContext
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JList
|
||||
import javax.swing.JPanel
|
||||
@@ -53,7 +53,7 @@ internal class ModelPickerRenderer(
|
||||
}
|
||||
|
||||
private fun favoriteInset(list: JList<*>): Int {
|
||||
if (!ExperimentalUI.isNewUI()) return 0
|
||||
if (!NewUI.isEnabled()) return 0
|
||||
val inner = JBUI.CurrentTheme.Popup.Selection.innerInsets()
|
||||
val edge = JBUI.CurrentTheme.Popup.Selection.LEFT_RIGHT_INSET.get()
|
||||
return edge + if (list.componentOrientation.isLeftToRight) inner.right else inner.left
|
||||
@@ -162,11 +162,9 @@ internal class ModelPickerRenderer(
|
||||
private val text = KiloBundle.message("model.picker.free")
|
||||
|
||||
override fun getIconWidth(): Int {
|
||||
val fm = JBFont.small().let { font ->
|
||||
@Suppress("DEPRECATION")
|
||||
Toolkit.getDefaultToolkit().getFontMetrics(font)
|
||||
}
|
||||
return fm.stringWidth(text) + JBUI.scale(12)
|
||||
val font = JBFont.small()
|
||||
val w = font.getStringBounds(text, FontRenderContext(null, true, true)).width.toInt()
|
||||
return w + JBUI.scale(12)
|
||||
}
|
||||
|
||||
override fun getIconHeight(): Int = JBUI.scale(16)
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ class PromptPanel(
|
||||
?: return@addActionListener
|
||||
val ctx = DataManager.getInstance().getDataContext(button)
|
||||
val event = AnActionEvent.createEvent(action, ctx, null, ActionPlaces.UNKNOWN, ActionUiKind.NONE, null)
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
ActionUtil.performAction(action, event)
|
||||
}
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package ai.kilocode.client.ui
|
||||
|
||||
import com.intellij.ui.icons.CachedImageIcon
|
||||
import com.intellij.ui.svg.SvgAttributePatcher
|
||||
import com.intellij.util.SVGLoader
|
||||
import java.awt.Color
|
||||
import javax.swing.Icon
|
||||
|
||||
private const val OPAQUE_ALPHA = 255
|
||||
|
||||
internal fun Icon.colorizeIfPossible(
|
||||
fillColor: Color,
|
||||
borderColor: Color = fillColor,
|
||||
fillId: String? = null,
|
||||
strokeId: String? = null,
|
||||
): Icon = (this as? CachedImageIcon)?.createWithPatcher(
|
||||
colorPatcher = object : SVGLoader.SvgElementColorPatcherProvider, SvgAttributePatcher {
|
||||
private val digest = longArrayOf(0L, 440413911775177385)
|
||||
|
||||
override fun digest(): LongArray {
|
||||
digest[0] = toLong(fillColor.rgb, borderColor.rgb)
|
||||
return digest
|
||||
}
|
||||
|
||||
override fun patchColors(attributes: MutableMap<String, String>) {
|
||||
val id = attributes["id"]
|
||||
if (fillId == null || id == fillId) setAttribute(attributes, "fill", fillColor)
|
||||
if (strokeId == null || id == strokeId) setAttribute(attributes, "stroke", borderColor)
|
||||
}
|
||||
|
||||
override fun attributeForPath(path: String) = this
|
||||
|
||||
private fun setAttribute(attributes: MutableMap<String, String>, key: String, color: Color) {
|
||||
if (!attributes.containsKey(key) || attributes[key] == "none") return
|
||||
attributes[key] = "rgb(${color.red},${color.green},${color.blue})"
|
||||
val alpha = color.alpha
|
||||
if (alpha != OPAQUE_ALPHA) {
|
||||
attributes["$key-opacity"] = "${alpha / OPAQUE_ALPHA.toFloat()}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun toLong(high: Int, low: Int): Long {
|
||||
return (high.toLong() shl 32) or (low.toLong() and 0xFFFFFFFFL)
|
||||
}
|
||||
},
|
||||
) ?: this
|
||||
+14
-13
@@ -20,6 +20,7 @@ import ai.kilocode.rpc.dto.KiloWorkspaceStatusDto
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import ai.kilocode.rpc.dto.SessionTimeDto
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
@@ -71,7 +72,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -81,7 +82,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -91,7 +92,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, null, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -100,7 +101,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val action = OpenSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -110,7 +111,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val items = listOf(localItem("ses_1"), localItem("ses_2"))
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -160,7 +161,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -170,7 +171,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -180,7 +181,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, null, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -189,7 +190,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val action = DeleteSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -260,7 +261,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -269,7 +270,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val action = RenameSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -279,7 +280,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val items = listOf(localItem("ses_1"), localItem("ses_2"))
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
@@ -289,7 +290,7 @@ class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ import ai.kilocode.client.session.SessionRef
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
@@ -14,7 +15,7 @@ class NewSessionActionTest : BasePlatformTestCase() {
|
||||
val action = NewSessionAction()
|
||||
val event = event(manager)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
action.actionPerformed(event)
|
||||
|
||||
assertEquals(1, manager.created)
|
||||
@@ -28,7 +29,7 @@ class NewSessionActionTest : BasePlatformTestCase() {
|
||||
val action = NewSessionAction()
|
||||
val presentation = Presentation().apply { copyFrom(action.templatePresentation) }
|
||||
|
||||
action.update(AnActionEvent.createFromDataContext("", presentation) { null })
|
||||
ActionUtil.updateAction(action, AnActionEvent.createFromDataContext("", presentation) { null })
|
||||
|
||||
assertFalse(presentation.isEnabled)
|
||||
}
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ package ai.kilocode.client.actions
|
||||
import ai.kilocode.client.session.ui.prompt.PromptDataKeys
|
||||
import ai.kilocode.client.session.ui.prompt.SendPromptContext
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
@@ -14,7 +15,7 @@ class SendPromptActionTest : BasePlatformTestCase() {
|
||||
val action = SendPromptAction()
|
||||
val event = event(action, ctx)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
action.actionPerformed(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabled)
|
||||
@@ -27,7 +28,7 @@ class SendPromptActionTest : BasePlatformTestCase() {
|
||||
val action = SendPromptAction()
|
||||
val event = event(action, null)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabled)
|
||||
}
|
||||
@@ -37,7 +38,7 @@ class SendPromptActionTest : BasePlatformTestCase() {
|
||||
val action = SendPromptAction()
|
||||
val event = event(action, ctx)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
action.actionPerformed(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabled)
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ package ai.kilocode.client.actions
|
||||
import ai.kilocode.client.session.ui.prompt.PromptDataKeys
|
||||
import ai.kilocode.client.session.ui.prompt.SendPromptContext
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
@@ -14,7 +15,7 @@ class StopSessionActionTest : BasePlatformTestCase() {
|
||||
val action = StopSessionAction()
|
||||
val event = event(action, ctx)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
action.actionPerformed(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabled)
|
||||
@@ -27,7 +28,7 @@ class StopSessionActionTest : BasePlatformTestCase() {
|
||||
val action = StopSessionAction()
|
||||
val event = event(action, null)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
|
||||
assertFalse(event.presentation.isEnabled)
|
||||
}
|
||||
@@ -37,7 +38,7 @@ class StopSessionActionTest : BasePlatformTestCase() {
|
||||
val action = StopSessionAction()
|
||||
val event = event(action, ctx)
|
||||
|
||||
action.update(event)
|
||||
ActionUtil.updateAction(action, event)
|
||||
action.actionPerformed(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabled)
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ import ai.kilocode.client.testing.FakeAppRpcApi
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.client.testing.FakeWorkspaceRpcApi
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.session.scroll.SessionScroll
|
||||
import ai.kilocode.rpc.dto.ChatEventDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStateDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStatusDto
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ package ai.kilocode.client.session.ui.model
|
||||
import ai.kilocode.rpc.dto.ModelSelectionDto
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ui.CollectionListModel
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
import com.intellij.ui.NewUI
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
@@ -273,7 +273,7 @@ class ModelPickerTest : BasePlatformTestCase() {
|
||||
) = ModelPicker.Item(id, display, provider, name, index, free = free)
|
||||
|
||||
private fun favoriteInset(list: JBList<*>): Int {
|
||||
if (!ExperimentalUI.isNewUI()) return 0
|
||||
if (!NewUI.isEnabled()) return 0
|
||||
val inner = JBUI.CurrentTheme.Popup.Selection.innerInsets()
|
||||
val edge = JBUI.CurrentTheme.Popup.Selection.LEFT_RIGHT_INSET.get()
|
||||
return edge + if (list.componentOrientation.isLeftToRight) inner.right else inner.left
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1,5 +1,58 @@
|
||||
# kilo-code
|
||||
|
||||
## 7.3.0
|
||||
|
||||
## 7.2.54
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#10230](https://github.com/Kilo-Org/kilocode/pull/10230) [`26f5393`](https://github.com/Kilo-Org/kilocode/commit/26f5393be272a31390f6a317555099ed2addd1b0) - Add a collapsible sidebar in the Agent Manager. The toggle button sits left of the tab title and the collapsed state persists across reloads and restarts. Starting a new session or worktree automatically reopens the sidebar so the new entry is visible.
|
||||
|
||||
- [#10218](https://github.com/Kilo-Org/kilocode/pull/10218) [`4860e65`](https://github.com/Kilo-Org/kilocode/commit/4860e654ca1cc46c4e99acc3f40d4f1302e34944) - Support setting an auto-compaction threshold percentage so long sessions can compact before the context window is full.
|
||||
|
||||
- [#10228](https://github.com/Kilo-Org/kilocode/pull/10228) [`911e7ce`](https://github.com/Kilo-Org/kilocode/commit/911e7ce73383cfd5780ec80ff8e8b8187061a18a) - Add syntax highlighting to shell command output with Shiki, labeled "Command" and "Output" sections, per-section copy buttons, and an "Open in Editor" action that opens the full untruncated output in a VS Code editor tab.
|
||||
|
||||
- [#10258](https://github.com/Kilo-Org/kilocode/pull/10258) [`1af7973`](https://github.com/Kilo-Org/kilocode/commit/1af79731a8ed925f1f69aa536ba90a53b89e8dfb) - Support experimental speech-to-text voice input in VS Code prompt fields through Kilo Gateway.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#10196](https://github.com/Kilo-Org/kilocode/pull/10196) [`174d467`](https://github.com/Kilo-Org/kilocode/commit/174d467a4967c53fddfc97ae9663ebf17c0fafbc) - Keep Agent Manager state hidden from Git before creating local sessions.
|
||||
|
||||
- [#10156](https://github.com/Kilo-Org/kilocode/pull/10156) [`e805d2f`](https://github.com/Kilo-Org/kilocode/commit/e805d2f756b785776cb1f60b97a253e3ae4c837c) - Keep VS Code session timeline, token, context, and cost stats in sync after restoring a checkpoint.
|
||||
|
||||
- [#10136](https://github.com/Kilo-Org/kilocode/pull/10136) [`8af638e`](https://github.com/Kilo-Org/kilocode/commit/8af638e7e20c645b22d96da5e30665e8e9cbf6ad) - Show ChatGPT sign-in again when Codex authentication expires.
|
||||
|
||||
- [#8754](https://github.com/Kilo-Org/kilocode/pull/8754) [`e498c02`](https://github.com/Kilo-Org/kilocode/commit/e498c02f7acc5c228bbd45f9e4f294bf5def21ca) Thanks [@shssoichiro](https://github.com/shssoichiro)! - Fix TUI diff rendering when header-like content lines appear inside a unified diff hunk.
|
||||
|
||||
- [#10265](https://github.com/Kilo-Org/kilocode/pull/10265) [`900ba85`](https://github.com/Kilo-Org/kilocode/commit/900ba85b838e035952ea6ca9adf20cbc923e8d99) - Ensure snapshot installs package a fresh CLI backend so new extension features are available in local VSIX builds.
|
||||
|
||||
- [#10227](https://github.com/Kilo-Org/kilocode/pull/10227) [`e1e2454`](https://github.com/Kilo-Org/kilocode/commit/e1e2454b4285109f1d4ef34b93d5d47f612faaea) - Hide Task tool subagent sessions from Agent Manager session lists.
|
||||
|
||||
- [#10229](https://github.com/Kilo-Org/kilocode/pull/10229) [`41e3cda`](https://github.com/Kilo-Org/kilocode/commit/41e3cda971c37d155de443e58ae2aeafdaf928b1) - Move worktree file revert diff status checks into the VS Code extension host so Kilo no longer asks the CLI server to run git diff commands through Bun during workspace and Agent Manager reverts.
|
||||
|
||||
- [#10240](https://github.com/Kilo-Org/kilocode/pull/10240) [`09f0156`](https://github.com/Kilo-Org/kilocode/commit/09f0156bfc66643870835251eef873d6e62f2791) - Keep the New Worktree model picker open when interacting with its search field.
|
||||
|
||||
- [#10243](https://github.com/Kilo-Org/kilocode/pull/10243) [`47c22a2`](https://github.com/Kilo-Org/kilocode/commit/47c22a28e98a50955bd9470a5b3f78bce5d5b4ba) - Keep chat tool cards open or closed when streaming updates remount them.
|
||||
|
||||
- [#10159](https://github.com/Kilo-Org/kilocode/pull/10159) [`64e45ea`](https://github.com/Kilo-Org/kilocode/commit/64e45eaba30641dd7b36fb65f79ddee3c0e14cf1) - Use the VS Code-native placeholder color for input fields so placeholder text is consistently distinct from values you have typed (Settings, Agent Manager, prompt input, model search, etc.).
|
||||
|
||||
- [#10206](https://github.com/Kilo-Org/kilocode/pull/10206) [`177f7df`](https://github.com/Kilo-Org/kilocode/commit/177f7df04937e13b3ea5abc5a9c79a56c9814ab6) - Reset the To-dos tab when reverting a session snapshot.
|
||||
|
||||
- [#10158](https://github.com/Kilo-Org/kilocode/pull/10158) [`d8245a0`](https://github.com/Kilo-Org/kilocode/commit/d8245a0ceb0989b8596c5a5d17fd1095ba9521be) - Fix Mermaid diagrams rendering with empty text inside every shape by restoring the `foreignObject` HTML integration point that DOMPurify dropped in 3.1.7.
|
||||
|
||||
- [#10256](https://github.com/Kilo-Org/kilocode/pull/10256) [`da9292f`](https://github.com/Kilo-Org/kilocode/commit/da9292fbccad9f4c20f439e240790da3ab3a92d3) - Support viewing Agent Manager terminal tabs alongside the right diff sidebar, and send review comments to the active terminal.
|
||||
|
||||
- [#10275](https://github.com/Kilo-Org/kilocode/pull/10275) [`f5dc95b`](https://github.com/Kilo-Org/kilocode/commit/f5dc95b99394c17ad7140bb034bc15a0f9de60b6) - Keep GPT-4o voice transcriptions closer to the words that were spoken.
|
||||
|
||||
- [#10095](https://github.com/Kilo-Org/kilocode/pull/10095) [`710c9dc`](https://github.com/Kilo-Org/kilocode/commit/710c9dca1e9d5f522fa3fe1495dc41ec1eea4b66) - Keep configured mode models in sync when switching modes in VS Code.
|
||||
|
||||
- Updated dependencies [[`262c2d6`](https://github.com/Kilo-Org/kilocode/commit/262c2d670474f0da50aa0c900e49aad93863acaf), [`4860e65`](https://github.com/Kilo-Org/kilocode/commit/4860e654ca1cc46c4e99acc3f40d4f1302e34944), [`47c22a2`](https://github.com/Kilo-Org/kilocode/commit/47c22a28e98a50955bd9470a5b3f78bce5d5b4ba), [`1af7973`](https://github.com/Kilo-Org/kilocode/commit/1af79731a8ed925f1f69aa536ba90a53b89e8dfb), [`1ea86fb`](https://github.com/Kilo-Org/kilocode/commit/1ea86fb6e15cbe486cb0af6f26995d0b1b2745a2), [`f5dc95b`](https://github.com/Kilo-Org/kilocode/commit/f5dc95b99394c17ad7140bb034bc15a0f9de60b6)]:
|
||||
- @kilocode/kilo-ui@7.2.53
|
||||
- @kilocode/sdk@7.3.0
|
||||
- @kilocode/kilo-gateway@7.3.0
|
||||
- @opencode-ai/ui@7.2.53
|
||||
- @kilocode/kilo-indexing@7.2.53
|
||||
|
||||
## 7.2.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "kilo-code",
|
||||
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
|
||||
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
|
||||
"version": "7.2.52",
|
||||
"version": "7.3.0",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
|
||||
@@ -17,6 +17,7 @@ import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreCo
|
||||
import { ChatTextAreaAutocomplete } from "./services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete"
|
||||
import { buildWebviewHtml, getWebviewFontSize } from "./utils"
|
||||
import { saveImage } from "./kilo-provider/save-image"
|
||||
import { exportTranscript } from "./kilo-provider/export-transcript"
|
||||
import {
|
||||
TelemetryProxy,
|
||||
type TelemetryPropertiesProvider,
|
||||
@@ -137,6 +138,7 @@ import type { KiloProviderOptions } from "./kilo-provider/options"
|
||||
import { fetchKiloEmbeddingModelCatalog } from "@kilocode/kilo-gateway"
|
||||
|
||||
type MessageLoadMode = "replace" | "prepend" | "focus" | "reconcile"
|
||||
type ContextMessage = { contextDirectory?: unknown }
|
||||
// Helper to map agent data to the subset of fields sent to the webview
|
||||
const mapAgent = (a: Agent) => ({
|
||||
name: a.name,
|
||||
@@ -449,10 +451,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview)
|
||||
this.setupWebviewMessageHandler(webviewView.webview)
|
||||
|
||||
vscode.commands.executeCommand("setContext", "kilo-code.new.sidebarVisible", webviewView.visible)
|
||||
this.setSidebarVisible(webviewView.visible)
|
||||
this.visibilityDisposable?.dispose()
|
||||
this.visibilityDisposable = webviewView.onDidChangeVisibility(() => {
|
||||
vscode.commands.executeCommand("setContext", "kilo-code.new.sidebarVisible", webviewView.visible)
|
||||
this.setSidebarVisible(webviewView.visible)
|
||||
if (this.statsPoller) {
|
||||
this.statsPoller.setEnabled(webviewView.visible)
|
||||
this.statsPoller.setVisible(webviewView.visible)
|
||||
@@ -462,6 +464,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.initializeConnection()
|
||||
}
|
||||
|
||||
private setSidebarVisible(visible: boolean): void {
|
||||
vscode.commands.executeCommand("setContext", "kilo-code.new.sidebarVisible", visible)
|
||||
this.opts.onSidebarVisibilityChange?.(visible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a WebviewPanel for displaying the Kilo webview in an editor tab.
|
||||
*/
|
||||
@@ -616,6 +623,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
connection: this.connectionService,
|
||||
dir: this.getWorkspaceDirectory(this.currentSession?.id),
|
||||
post: (msg) => this.postMessage(msg),
|
||||
exportTranscript: (sessionID) => this.handleExportSessionTranscript(sessionID),
|
||||
})
|
||||
) {
|
||||
return
|
||||
@@ -647,7 +655,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.readyResolvers.splice(0).forEach((r) => r())
|
||||
break
|
||||
case "sendMessage": {
|
||||
const files = parseMessageFiles(message.files)
|
||||
const msg = message as typeof message & ContextMessage
|
||||
await this.handleSendMessage(
|
||||
message.text,
|
||||
typeof message.messageID === "string" ? message.messageID : undefined,
|
||||
@@ -657,13 +665,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.modelID,
|
||||
message.agent,
|
||||
message.variant,
|
||||
files,
|
||||
parseMessageFiles(message.files),
|
||||
typeof message.agentManagerContext === "string" ? message.agentManagerContext : undefined,
|
||||
typeof msg.contextDirectory === "string" ? msg.contextDirectory : undefined,
|
||||
)
|
||||
break
|
||||
}
|
||||
case "sendCommand": {
|
||||
const files = parseMessageFiles(message.files)
|
||||
const msg = message as typeof message & ContextMessage
|
||||
await this.handleSendCommand(
|
||||
message.command,
|
||||
message.arguments,
|
||||
@@ -674,8 +683,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.modelID,
|
||||
message.agent,
|
||||
message.variant,
|
||||
files,
|
||||
parseMessageFiles(message.files),
|
||||
typeof message.agentManagerContext === "string" ? message.agentManagerContext : undefined,
|
||||
typeof msg.contextDirectory === "string" ? msg.contextDirectory : undefined,
|
||||
)
|
||||
break
|
||||
}
|
||||
@@ -1081,13 +1091,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
break
|
||||
}
|
||||
case "fetchMarketplaceData": {
|
||||
const workspace = this.getProjectDirectory(this.currentSession?.id)
|
||||
const mp = this.getMarketplace()
|
||||
// Fetch skills from CLI backend (authoritative source) so the
|
||||
// marketplace doesn't need to duplicate the CLI's skill scanning.
|
||||
const skills = await this.fetchCliSkills()
|
||||
const data = await mp.fetchData(workspace, skills)
|
||||
this.postMessage({ type: "marketplaceData", ...data })
|
||||
await this.handleFetchMarketplaceData()
|
||||
break
|
||||
}
|
||||
case "filterMarketplaceItems": {
|
||||
@@ -1137,6 +1141,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.diffVirtualProvider.open(d)
|
||||
}
|
||||
|
||||
private async handleFetchMarketplaceData(): Promise<void> {
|
||||
const workspace = this.getProjectDirectory(this.currentSession?.id)
|
||||
const mp = this.getMarketplace()
|
||||
const skills = await this.fetchCliSkills()
|
||||
const data = await mp.fetchData(workspace, skills)
|
||||
this.postMessage({ type: "marketplaceData", ...data })
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize connection to the CLI backend server.
|
||||
* Subscribes to the shared KiloConnectionService.
|
||||
@@ -1346,7 +1358,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
try {
|
||||
const workspaceDir = this.getContextDirectory()
|
||||
const { data: session } = await this.client.session.create({ directory: workspaceDir }, { throwOnError: true })
|
||||
const { data: session } = await this.client.session.create(
|
||||
{ directory: workspaceDir, platform: this.opts.platform },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
this.setCurrentSession(session)
|
||||
this.contextSessionID = session.id
|
||||
this.trackDirectory(session.id, workspaceDir)
|
||||
@@ -1536,6 +1551,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
client.session.list({ directory: dir, roots: true }, { throwOnError: true }).then(({ data }) => data)
|
||||
: null,
|
||||
sessionDirectories: this.sessionDirectories,
|
||||
worktreeDirectories: this.opts.worktreeDirectories,
|
||||
workspaceDirectory: this.getWorkspaceDirectory(),
|
||||
postMessage: (msg: unknown) => this.postMessage(msg),
|
||||
}
|
||||
@@ -1654,6 +1670,30 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a full session transcript as Markdown.
|
||||
*/
|
||||
private async handleExportSessionTranscript(sessionID: string): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({ type: "error", message: "Not connected to CLI backend" })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const saved = await exportTranscript(this.client, {
|
||||
sessionID,
|
||||
dir: this.getWorkspaceDirectory(sessionID),
|
||||
})
|
||||
if (saved) void vscode.window.showInformationMessage("Session transcript exported as Markdown.")
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] KiloProvider: Failed to export session transcript:", error)
|
||||
this.postMessage({
|
||||
type: "error",
|
||||
message: getErrorMessage(error) || "Failed to export session transcript",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch providers and send to webview. Coalesced: at most one in-flight + one queued. */
|
||||
private async fetchAndSendProviders(): Promise<void> {
|
||||
const next = ++this.providersGeneration
|
||||
@@ -2439,7 +2479,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
details: getConfigErrorDetails(error),
|
||||
})
|
||||
}
|
||||
private async resolveSession(sessionID?: string, draftID?: string, context?: string) {
|
||||
private async resolveSession(sessionID?: string, draftID?: string, context?: string, contextDirectory?: string) {
|
||||
if (!this.client) return undefined
|
||||
|
||||
const dir = resolveNewSessionDirectory({
|
||||
@@ -2447,12 +2487,16 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
currentSessionID: this.currentSession?.id,
|
||||
contextSessionID: this.contextSessionID,
|
||||
agentManagerContext: context,
|
||||
contextDirectory,
|
||||
sessionDirectories: this.sessionDirectories,
|
||||
workspaceDirectory: this.getRootDirectory(),
|
||||
})
|
||||
|
||||
if (!sessionID && !this.currentSession) {
|
||||
const { data: session } = await this.client.session.create({ directory: dir }, { throwOnError: true })
|
||||
const { data: session } = await this.client.session.create(
|
||||
{ directory: dir, platform: this.opts.platform },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
this.setCurrentSession(session)
|
||||
this.contextSessionID = session.id
|
||||
this.trackDirectory(session.id, dir)
|
||||
@@ -2556,6 +2600,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
variant?: string,
|
||||
files?: MessageFile[],
|
||||
context?: string,
|
||||
contextDirectory?: string,
|
||||
): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({
|
||||
@@ -2572,7 +2617,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
let resolved: { sid: string; dir: string } | undefined
|
||||
try {
|
||||
resolved = await this.resolveSession(sessionID, draftID, context)
|
||||
resolved = await this.resolveSession(sessionID, draftID, context, contextDirectory)
|
||||
|
||||
const parts: Array<TextPartInput | FilePartInput> = []
|
||||
if (files) {
|
||||
@@ -2633,6 +2678,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
variant?: string,
|
||||
files?: MessageFile[],
|
||||
context?: string,
|
||||
contextDirectory?: string,
|
||||
): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({
|
||||
@@ -2649,7 +2695,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
let resolved: { sid: string; dir: string } | undefined
|
||||
try {
|
||||
resolved = await this.resolveSession(sessionID, draftID, context)
|
||||
resolved = await this.resolveSession(sessionID, draftID, context, contextDirectory)
|
||||
|
||||
if (messageID) {
|
||||
this.connectionService.recordMessageSessionId(messageID, resolved!.sid)
|
||||
|
||||
@@ -71,6 +71,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
private cachedLocalStats: { type: "agentManager.localStats"; stats: LocalStats } | undefined
|
||||
private unsubTool: (() => void) | undefined
|
||||
private closing: Promise<void> | undefined
|
||||
private onVisibilityChange: ((visible: boolean) => void) | undefined
|
||||
|
||||
/** Session ID most recently loaded via a `loadMessages` message from the webview.
|
||||
* Updated synchronously — unlike the session provider's currentSession which depends on
|
||||
@@ -181,10 +182,15 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.attachPanel(
|
||||
this.host.openPanel({
|
||||
onBeforeMessage: (msg) => this.onMessage(msg),
|
||||
worktreeDirectories: () => this.getWorktreeDirectories(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
public onPanelVisibilityChange(cb: (visible: boolean) => void): void {
|
||||
this.onVisibilityChange = cb
|
||||
}
|
||||
|
||||
/** Restore the Agent Manager panel from a previously serialized state.
|
||||
* The caller (extension.ts / vscode-host.ts) wraps the raw panel before passing it. */
|
||||
public deserializePanel(ctx: PanelContext): void {
|
||||
@@ -212,6 +218,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.panel = ctx
|
||||
|
||||
this.statsPoller.setVisible(ctx.visible)
|
||||
this.onVisibilityChange?.(ctx.visible)
|
||||
ctx.onDidChangeVisibility((visible) => {
|
||||
this.statsPoller.setVisible(visible)
|
||||
})
|
||||
@@ -233,6 +240,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.prBridge.poller.stop()
|
||||
this.diffs.stop()
|
||||
this.panel = undefined
|
||||
this.onVisibilityChange?.(false)
|
||||
}
|
||||
ctx.sessions.dispose()
|
||||
})
|
||||
@@ -378,7 +386,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
if (m.type === "agentManager.deleteWorktree") return this.onDeleteWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.removeStaleWorktree") return this.onRemoveStaleWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.promoteSession") return this.onPromoteSession(m.sessionId)
|
||||
if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId, m.sessionId)
|
||||
if (m.type === "agentManager.forkSession") return this.onForkSession(m.sessionId, m.worktreeId, m.messageId)
|
||||
if (m.type === "agentManager.closeSession") return this.onCloseSession(m.sessionId)
|
||||
}
|
||||
@@ -418,6 +426,15 @@ export class AgentManagerProvider implements Disposable {
|
||||
return null
|
||||
}
|
||||
|
||||
if ((m.type === "sendMessage" || m.type === "sendCommand") && !m.sessionID) {
|
||||
const ctx = typeof m.agentManagerContext === "string" ? m.agentManagerContext : undefined
|
||||
const worktree = ctx && ctx !== "local" ? this.getStateManager()?.getWorktree(ctx) : undefined
|
||||
if (worktree) {
|
||||
if (m.draftID) this.activeSessionId = m.draftID
|
||||
return { ...msg, contextDirectory: worktree.path }
|
||||
}
|
||||
}
|
||||
|
||||
if ((m.type === "sendMessage" || m.type === "sendCommand") && m.draftID && !m.sessionID) {
|
||||
this.activeSessionId = m.draftID
|
||||
return msg
|
||||
@@ -1049,7 +1066,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
}
|
||||
|
||||
/** Add a new session to an existing worktree. */
|
||||
private async onAddSessionToWorktree(worktreeId: string): Promise<null> {
|
||||
private async onAddSessionToWorktree(worktreeId: string, sessionId?: string): Promise<null> {
|
||||
let client: KiloClient
|
||||
try {
|
||||
client = this.connectionService.getClient()
|
||||
@@ -1068,6 +1085,26 @@ export class AgentManagerProvider implements Disposable {
|
||||
return null
|
||||
}
|
||||
|
||||
if (sessionId) {
|
||||
if (state.getSession(sessionId)) state.moveSession(sessionId, worktreeId)
|
||||
else state.addSession(sessionId, worktreeId)
|
||||
this.registerWorktreeSession(sessionId, worktree.path)
|
||||
this.pushState()
|
||||
this.postToWebview({
|
||||
type: "agentManager.sessionAdded",
|
||||
sessionId,
|
||||
worktreeId,
|
||||
})
|
||||
this.host.capture("Agent Manager Session Started", {
|
||||
source: PLATFORM,
|
||||
sessionId,
|
||||
worktreeId,
|
||||
existing: true,
|
||||
})
|
||||
this.log(`Added existing session ${sessionId} to worktree ${worktreeId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
let session: Session
|
||||
try {
|
||||
const { data } = await client.session.create(
|
||||
@@ -1648,6 +1685,14 @@ export class AgentManagerProvider implements Disposable {
|
||||
return this.panel?.sessions.getSessionDirectories() ?? new Map()
|
||||
}
|
||||
|
||||
public getWorktreeDirectories(): string[] {
|
||||
return (
|
||||
this.getStateManager()
|
||||
?.getWorktrees()
|
||||
.map((wt) => wt.path) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue a sidebar session in a new worktree.
|
||||
* Captures git state, creates worktree, applies state, forks session.
|
||||
|
||||
@@ -101,6 +101,7 @@ function createHarness() {
|
||||
prBridge: { handleMessage: ReturnType<typeof vi.fn> }
|
||||
activeSessionId: string | undefined
|
||||
stateReady: Promise<void> | undefined
|
||||
contextTarget: ReturnType<typeof vi.fn>
|
||||
createWorktreeOnDisk: ReturnType<typeof vi.fn>
|
||||
runSetupScriptForWorktree: ReturnType<typeof vi.fn>
|
||||
createSessionInWorktree: ReturnType<typeof vi.fn>
|
||||
@@ -121,6 +122,7 @@ function createHarness() {
|
||||
manager.prBridge = { handleMessage: vi.fn().mockReturnValue(false) }
|
||||
manager.activeSessionId = undefined
|
||||
manager.stateReady = Promise.resolve()
|
||||
manager.contextTarget = vi.fn()
|
||||
manager.createWorktreeOnDisk = vi.fn()
|
||||
manager.runSetupScriptForWorktree = vi.fn().mockResolvedValue(undefined)
|
||||
manager.createSessionInWorktree = vi.fn()
|
||||
@@ -202,4 +204,28 @@ describe("AgentManagerProvider worktree creation", () => {
|
||||
|
||||
expect(result).toEqual({ type: "requestFileSearch", query: "src", requestId: "r1", sessionID: "session-wt" })
|
||||
})
|
||||
|
||||
it("resolves new sends to the selected worktree directory", async () => {
|
||||
const manager = createHarness()
|
||||
const state = {
|
||||
getWorktree: vi.fn().mockReturnValue({ id: "wt-1", path: "/repo/.kilo/worktrees/wt-1" }),
|
||||
}
|
||||
manager.getStateManager.mockReturnValue(state)
|
||||
manager.contextTarget.mockResolvedValue(undefined)
|
||||
|
||||
const result = await manager.onMessage({
|
||||
type: "sendMessage",
|
||||
text: "continue",
|
||||
agentManagerContext: "wt-1",
|
||||
draftID: "draft-1",
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "sendMessage",
|
||||
text: "continue",
|
||||
agentManagerContext: "wt-1",
|
||||
draftID: "draft-1",
|
||||
contextDirectory: "/repo/.kilo/worktrees/wt-1",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -93,6 +93,7 @@ export interface Host {
|
||||
*/
|
||||
openPanel(opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
}): PanelContext
|
||||
|
||||
/** Get the workspace/project root path. */
|
||||
|
||||
@@ -348,6 +348,7 @@ interface OpenLocallyIn {
|
||||
interface AddSessionToWorktreeIn {
|
||||
type: "agentManager.addSessionToWorktree"
|
||||
worktreeId: string
|
||||
sessionId?: string
|
||||
}
|
||||
|
||||
interface CloseSessionIn {
|
||||
@@ -606,6 +607,7 @@ interface SendMessageIn {
|
||||
variant?: string
|
||||
files?: Array<{ mime: string; url: string; filename?: string; source?: FileSourceIn }>
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
interface SendCommandIn {
|
||||
@@ -621,6 +623,7 @@ interface SendCommandIn {
|
||||
variant?: string
|
||||
files?: Array<{ mime: string; url: string; filename?: string; source?: FileSourceIn }>
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
interface RequestTerminalContextIn {
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as vscode from "vscode"
|
||||
import type { Host, PanelContext, OutputHandle, SessionProvider, Disposable } from "./host"
|
||||
import type { KiloConnectionService } from "../services/cli-backend"
|
||||
import { KiloProvider } from "../KiloProvider"
|
||||
import { PLATFORM } from "./constants"
|
||||
import { DiffVirtualProvider } from "../DiffVirtualProvider"
|
||||
import { buildWebviewHtml } from "../utils"
|
||||
import { openFileInEditor, getWorkspaceRoot } from "../review-utils"
|
||||
@@ -35,6 +36,7 @@ export class VscodeHost implements Host {
|
||||
|
||||
openPanel(opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
}): PanelContext {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
"kilo-code.new.AgentManagerPanel",
|
||||
@@ -54,6 +56,7 @@ export class VscodeHost implements Host {
|
||||
panel: vscode.WebviewPanel,
|
||||
opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
},
|
||||
): PanelContext {
|
||||
return this.wirePanel(panel, opts)
|
||||
@@ -63,6 +66,7 @@ export class VscodeHost implements Host {
|
||||
panel: vscode.WebviewPanel,
|
||||
opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
},
|
||||
): PanelContext {
|
||||
panel.webview.options = {
|
||||
@@ -85,7 +89,9 @@ export class VscodeHost implements Host {
|
||||
})
|
||||
|
||||
const provider = new KiloProvider(this.extensionUri, this.connectionService, this.context, {
|
||||
platform: PLATFORM,
|
||||
slimEditMetadata: true,
|
||||
worktreeDirectories: () => opts.worktreeDirectories?.() ?? [],
|
||||
})
|
||||
if (this.diffVirtual) {
|
||||
provider.setDiffVirtualProvider(this.diffVirtual)
|
||||
|
||||
@@ -23,6 +23,14 @@ import { RemoteStatusService } from "./services/RemoteStatusService"
|
||||
import { markWorkspace } from "./util/spotlight"
|
||||
|
||||
let agentManager: AgentManagerProvider | undefined
|
||||
let shuttingDown = false
|
||||
|
||||
const RESTORE_KEY = "kilo.workbench.restore"
|
||||
|
||||
type RestoreState = {
|
||||
sidebar?: boolean
|
||||
agentManager?: boolean
|
||||
}
|
||||
|
||||
const panelTitleHandler = (panel: vscode.WebviewPanel) => (title: string) => {
|
||||
panel.title = title || EXTENSION_DISPLAY_NAME
|
||||
@@ -34,11 +42,21 @@ const panelTitleHandler = (panel: vscode.WebviewPanel) => (title: string) => {
|
||||
// it starts lazily when a webview connects or when ensureBackendForAutocomplete() triggers it.
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log("Kilo Code extension is now active")
|
||||
shuttingDown = false
|
||||
|
||||
const telemetry = TelemetryProxy.getInstance()
|
||||
|
||||
// Create shared connection service (one server for all webviews)
|
||||
const connectionService = new KiloConnectionService(context)
|
||||
let restore = context.workspaceState.get<RestoreState>(RESTORE_KEY) ?? {}
|
||||
const closeSidebar = restore.sidebar === false
|
||||
const remember = (patch: RestoreState) => {
|
||||
const next = { ...restore, ...patch }
|
||||
if (shuttingDown && patch.sidebar === false) next.sidebar = restore.sidebar
|
||||
if (shuttingDown && patch.agentManager === false) next.agentManager = restore.agentManager
|
||||
restore = next
|
||||
void context.workspaceState.update(RESTORE_KEY, restore)
|
||||
}
|
||||
|
||||
// Create browser automation service (manages Playwright MCP registration)
|
||||
const browserAutomationService = new BrowserAutomationService(connectionService)
|
||||
@@ -105,7 +123,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// Create the provider with shared service
|
||||
const provider = new KiloProvider(context.extensionUri, connectionService, context)
|
||||
const provider = new KiloProvider(context.extensionUri, connectionService, context, {
|
||||
onSidebarVisibilityChange: (visible) => remember({ sidebar: visible }),
|
||||
})
|
||||
provider.setRemoteService(remoteService)
|
||||
|
||||
// Register the webview view provider for the sidebar.
|
||||
@@ -115,6 +135,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
webviewOptions: { retainContextWhenHidden: true },
|
||||
}),
|
||||
)
|
||||
if (closeSidebar) void vscode.commands.executeCommand("workbench.action.closeSidebar")
|
||||
|
||||
// Ensure Agent Manager navigation keybindings work when a VS Code terminal has focus.
|
||||
// The terminal intercepts all keystrokes unless the command is listed in
|
||||
@@ -131,6 +152,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// Create Agent Manager provider for editor panel
|
||||
const agentManagerHost = new VscodeHost(context.extensionUri, connectionService, context)
|
||||
const agentManagerProvider = new AgentManagerProvider(agentManagerHost, connectionService)
|
||||
agentManagerProvider.onPanelVisibilityChange((visible) => remember({ agentManager: visible }))
|
||||
agentManager = agentManagerProvider
|
||||
context.subscriptions.push(agentManagerProvider)
|
||||
|
||||
@@ -169,8 +191,13 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewPanelSerializer(AgentManagerProvider.viewType, {
|
||||
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
||||
if (restore.agentManager === false) {
|
||||
panel.dispose()
|
||||
return Promise.resolve()
|
||||
}
|
||||
const ctx = agentManagerHost.wrapExistingPanel(panel, {
|
||||
onBeforeMessage: (msg) => agentManagerProvider.handleMessage(msg),
|
||||
worktreeDirectories: () => agentManagerProvider.getWorktreeDirectories(),
|
||||
})
|
||||
agentManagerProvider.deserializePanel(ctx)
|
||||
return Promise.resolve()
|
||||
@@ -452,6 +479,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// Dispose services when extension deactivates (kills the server)
|
||||
context.subscriptions.push({
|
||||
dispose: () => {
|
||||
shuttingDown = true
|
||||
unsubscribeStateChange()
|
||||
browserAutomationService.dispose()
|
||||
provider.dispose()
|
||||
@@ -461,6 +489,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
shuttingDown = true
|
||||
await agentManager?.shutdown()
|
||||
TelemetryProxy.getInstance().shutdown()
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ export interface SessionRefreshContext {
|
||||
connectionState: "connecting" | "connected" | "disconnected" | "error"
|
||||
listSessions: ((dir: string) => Promise<Session[]>) | null
|
||||
sessionDirectories: Map<string, string>
|
||||
worktreeDirectories?: () => string[]
|
||||
workspaceDirectory: string
|
||||
postMessage(message: unknown): void
|
||||
}
|
||||
@@ -245,7 +246,7 @@ export async function loadSessions(ctx: SessionRefreshContext): Promise<string |
|
||||
|
||||
const sessions = await list(ctx.workspaceDirectory)
|
||||
const projectID = sessions[0]?.projectID
|
||||
const worktreeDirs = new Set(ctx.sessionDirectories.values())
|
||||
const worktreeDirs = new Set([...(ctx.worktreeDirectories?.() ?? []), ...ctx.sessionDirectories.values()])
|
||||
const failed = new Set<string>()
|
||||
const extra = await Promise.all(
|
||||
[...worktreeDirs].map((dir) =>
|
||||
@@ -339,6 +340,7 @@ export function resolveNewSessionDirectory(input: {
|
||||
currentSessionID?: string
|
||||
contextSessionID?: string
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
sessionDirectories: Map<string, string>
|
||||
workspaceDirectory: string
|
||||
}) {
|
||||
@@ -350,6 +352,8 @@ export function resolveNewSessionDirectory(input: {
|
||||
})
|
||||
}
|
||||
|
||||
if (input.contextDirectory) return input.contextDirectory
|
||||
|
||||
return resolveContextDirectory({
|
||||
currentSessionID: input.currentSessionID,
|
||||
contextSessionID: input.contextSessionID,
|
||||
|
||||
@@ -11,10 +11,16 @@ type Ctx = {
|
||||
connection: KiloConnectionService
|
||||
dir: string
|
||||
post: (msg: unknown) => void
|
||||
exportTranscript: (sessionID: string) => Promise<void>
|
||||
}
|
||||
|
||||
export async function routeEarlyMessage(message: { type: string }, ctx: Ctx): Promise<boolean> {
|
||||
await routeSuggestionWebviewMessage(ctx.question, message)
|
||||
if (await ModelState.handleMessage(message.type, message, ctx.client, ctx.post)) return true
|
||||
if (message.type === "exportSessionTranscript") {
|
||||
const input = message as { sessionID?: unknown }
|
||||
if (typeof input.sessionID === "string") await ctx.exportTranscript(input.sessionID)
|
||||
return true
|
||||
}
|
||||
return await routeInputToolMessage(message, { connection: ctx.connection, dir: ctx.dir, post: ctx.post })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import type { KiloClient, Message, Part, Session } from "@kilocode/sdk/v2/client"
|
||||
import { fetchMessagePage } from "./message-page"
|
||||
|
||||
type Item = {
|
||||
info: Message
|
||||
parts: Part[]
|
||||
}
|
||||
|
||||
export async function exportTranscript(
|
||||
client: KiloClient,
|
||||
input: {
|
||||
sessionID: string
|
||||
dir: string
|
||||
},
|
||||
) {
|
||||
const [{ data: session }, page] = await Promise.all([
|
||||
client.session.get({ sessionID: input.sessionID, directory: input.dir }, { throwOnError: true }),
|
||||
fetchMessagePage(client, { sessionID: input.sessionID, workspaceDir: input.dir, limit: 0 }),
|
||||
])
|
||||
const text = formatTranscript(session, page.items)
|
||||
const uri = await vscode.window.showSaveDialog({
|
||||
defaultUri: vscode.Uri.file(path.join(input.dir, `session-${session.id.slice(0, 8)}.md`)),
|
||||
filters: { Markdown: ["md", "markdown"] },
|
||||
saveLabel: "Export",
|
||||
})
|
||||
if (!uri) return false
|
||||
await vscode.workspace.fs.writeFile(uri, Buffer.from(text, "utf8"))
|
||||
return true
|
||||
}
|
||||
|
||||
export function formatTranscript(session: Session, items: Item[]): string {
|
||||
const head = [
|
||||
`# ${session.title}`,
|
||||
"",
|
||||
`**Session ID:** ${session.id}`,
|
||||
`**Created:** ${new Date(session.time.created).toLocaleString()}`,
|
||||
`**Updated:** ${new Date(session.time.updated).toLocaleString()}`,
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"",
|
||||
].join("\n")
|
||||
const body = items.map((item) => formatMessage(item)).join("---\n\n")
|
||||
return `${head}${body}${items.length > 0 ? "---\n\n" : ""}`
|
||||
}
|
||||
|
||||
function formatMessage(item: Item): string {
|
||||
const head = item.info.role === "user" ? "## User\n\n" : "## Assistant\n\n"
|
||||
return `${head}${item.parts.map((part) => formatPart(part)).join("")}`
|
||||
}
|
||||
|
||||
function formatPart(part: Part): string {
|
||||
if (part.type === "text" && !part.synthetic) return `${part.text}\n\n`
|
||||
if (part.type === "tool") return `**Tool: ${part.tool}**\n\n`
|
||||
return ""
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
export type KiloProviderOptions = {
|
||||
projectDirectory?: string | null
|
||||
platform?: string
|
||||
slimEditMetadata?: boolean
|
||||
tabTitle?: (title: string) => void
|
||||
onSidebarVisibilityChange?: (visible: boolean) => void
|
||||
worktreeDirectories?: () => string[]
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ type Audio = {
|
||||
language?: string
|
||||
}
|
||||
|
||||
type Args = {
|
||||
pipe?: string[]
|
||||
input: string[]
|
||||
}
|
||||
|
||||
let active: Recording | undefined
|
||||
let starting: string | undefined
|
||||
|
||||
@@ -119,13 +124,15 @@ async function waitForStart(state: Recording): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
async function startWithArgs(bin: string, file: string, input: Input, args: string[][]): Promise<Recording> {
|
||||
async function startWithArgs(bin: string, file: string, input: Input, args: Args[]): Promise<Recording> {
|
||||
const [first, ...rest] = args
|
||||
if (!first) throw new Error(`Unsupported platform for speech input: ${process.platform}`)
|
||||
|
||||
const proc = spawn(bin, ["-y", ...first, "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-f", "wav", file], {
|
||||
stdio: ["pipe", "ignore", "pipe"],
|
||||
})
|
||||
const proc = first.pipe
|
||||
? pipeProcess(first.pipe, bin, file)
|
||||
: spawn(bin, ["-y", ...first.input, "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-f", "wav", file], {
|
||||
stdio: ["pipe", "ignore", "pipe"],
|
||||
})
|
||||
const state: Recording = { ...input, file, proc, stderr: [], stopped: false }
|
||||
active = state
|
||||
|
||||
@@ -151,6 +158,48 @@ async function startWithArgs(bin: string, file: string, input: Input, args: stri
|
||||
}
|
||||
}
|
||||
|
||||
function pipeProcess(pipe: string[], bin: string, file: string): ChildProcess {
|
||||
const source = spawn("pw-record", pipe, { stdio: ["ignore", "pipe", "pipe"] })
|
||||
const proc = spawn(
|
||||
bin,
|
||||
[
|
||||
"-y",
|
||||
"-f",
|
||||
"s16le",
|
||||
"-ar",
|
||||
"16000",
|
||||
"-ac",
|
||||
"1",
|
||||
"-i",
|
||||
"pipe:0",
|
||||
"-acodec",
|
||||
"pcm_s16le",
|
||||
"-ar",
|
||||
"16000",
|
||||
"-ac",
|
||||
"1",
|
||||
"-f",
|
||||
"wav",
|
||||
file,
|
||||
],
|
||||
{
|
||||
stdio: ["pipe", "ignore", "pipe"],
|
||||
},
|
||||
)
|
||||
|
||||
if (source.stdout && proc.stdin) source.stdout.pipe(proc.stdin)
|
||||
source.on("error", (err) => proc.emit("error", err))
|
||||
source.stderr?.on("data", (data: Buffer) => proc.stderr?.emit("data", data))
|
||||
source.once("exit", () => {
|
||||
if (proc.stdin?.writable) proc.stdin.end()
|
||||
})
|
||||
proc.once("exit", () => {
|
||||
if (!source.killed) source.kill("SIGTERM")
|
||||
})
|
||||
|
||||
return proc
|
||||
}
|
||||
|
||||
async function stopProcess(state: Recording): Promise<void> {
|
||||
if (state.proc.exitCode !== null || state.proc.signalCode) return
|
||||
|
||||
@@ -226,24 +275,33 @@ function platformPaths(): string[] {
|
||||
return ["/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/snap/bin/ffmpeg", "/home/linuxbrew/.linuxbrew/bin/ffmpeg"]
|
||||
}
|
||||
|
||||
async function inputArgSets(bin: string): Promise<string[][]> {
|
||||
if (process.platform === "darwin") return [["-f", "avfoundation", "-i", ":default"]]
|
||||
if (process.platform === "linux")
|
||||
async function inputArgSets(bin: string): Promise<Args[]> {
|
||||
if (process.platform === "darwin") return [{ input: ["-f", "avfoundation", "-i", ":default"] }]
|
||||
if (process.platform === "linux") {
|
||||
const device = process.env.KILO_FFMPEG_AUDIO_DEVICE
|
||||
if (device)
|
||||
return [
|
||||
{ pipe: ["--target", device, "--format", "s16", "--rate", "16000", "--channels", "1", "-"], input: [] },
|
||||
{ input: ["-f", "pulse", "-i", device] },
|
||||
{ input: ["-f", "alsa", "-i", device] },
|
||||
]
|
||||
return [
|
||||
["-f", "pulse", "-i", "default"],
|
||||
["-f", "alsa", "-i", "default"],
|
||||
{ pipe: ["--format", "s16", "--rate", "16000", "--channels", "1", "-"], input: [] },
|
||||
{ input: ["-f", "pulse", "-i", "default"] },
|
||||
{ input: ["-f", "alsa", "-i", "default"] },
|
||||
]
|
||||
}
|
||||
if (process.platform === "win32") return await windowsInputArgSets(bin)
|
||||
return []
|
||||
}
|
||||
|
||||
async function windowsInputArgSets(bin: string): Promise<string[][]> {
|
||||
async function windowsInputArgSets(bin: string): Promise<Args[]> {
|
||||
const configured = process.env.KILO_FFMPEG_AUDIO_DEVICE
|
||||
if (configured) return [["-f", "dshow", "-i", `audio=${configured}`]]
|
||||
if (configured) return [{ input: ["-f", "dshow", "-i", `audio=${configured}`] }]
|
||||
|
||||
const devices = await listDshowAudioDevices(bin)
|
||||
if (devices.length === 0) throw new Error("No Windows audio input devices found for speech input")
|
||||
return devices.map((device) => ["-f", "dshow", "-i", `audio=${device}`])
|
||||
return devices.map((device) => ({ input: ["-f", "dshow", "-i", `audio=${device}`] }))
|
||||
}
|
||||
|
||||
async function listDshowAudioDevices(bin: string): Promise<string[]> {
|
||||
@@ -266,11 +324,20 @@ function processOutput(err: unknown): string {
|
||||
}
|
||||
|
||||
function summary(state: Recording, fallback: string): string {
|
||||
const stderr = state.stderr.join("\n").trim()
|
||||
const stderr = cleanOutput(state.stderr.join("\n"))
|
||||
if (!stderr) return fallback
|
||||
return `${fallback}: ${stderr.slice(-800)}`
|
||||
}
|
||||
|
||||
export function cleanOutput(raw: string): string {
|
||||
const lines = raw
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.filter((line) => !/^(ffmpeg version|built with|configuration:|lib[a-z]+\s+\d)/i.test(line))
|
||||
return lines.join("\n").trim()
|
||||
}
|
||||
|
||||
async function removeFile(file: string): Promise<void> {
|
||||
await unlink(file).catch((err: unknown) => {
|
||||
console.warn("[Kilo New] Failed to remove speech recording", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface SpeechToTextModelDef {
|
||||
readonly id: string
|
||||
readonly label: string
|
||||
readonly provider: string
|
||||
readonly verbatim?: boolean
|
||||
}
|
||||
|
||||
const models: SpeechToTextModelDef[] = [
|
||||
@@ -9,11 +10,13 @@ const models: SpeechToTextModelDef[] = [
|
||||
id: "openai/gpt-4o-mini-transcribe",
|
||||
label: "GPT-4o Mini Transcribe",
|
||||
provider: "OpenAI",
|
||||
verbatim: true,
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-4o-transcribe",
|
||||
label: "GPT-4o Transcribe",
|
||||
provider: "OpenAI",
|
||||
verbatim: true,
|
||||
},
|
||||
{
|
||||
id: "openai/whisper-1",
|
||||
|
||||
@@ -3,6 +3,8 @@ import { getErrorMessage } from "../kilo-provider-utils"
|
||||
import { getSpeechToTextModel } from "./models"
|
||||
|
||||
const PATH = "/kilo/audio/transcriptions"
|
||||
const PROMPT =
|
||||
"Transcribe exactly what is spoken. Do not paraphrase, summarize, infer intent, or rewrite for clarity. Preserve the speaker's original wording as closely as possible, including incomplete phrases and unusual wording when audible."
|
||||
|
||||
type Req = {
|
||||
model?: string
|
||||
@@ -39,6 +41,8 @@ export async function transcribeSpeech(
|
||||
|
||||
const auth = Buffer.from(`kilo:${cfg.password}`).toString("base64")
|
||||
const url = new URL(PATH, cfg.baseUrl)
|
||||
const model = getSpeechToTextModel(input.model)
|
||||
const prompt = model.verbatim ? PROMPT : undefined
|
||||
if (dir) url.searchParams.set("directory", dir)
|
||||
|
||||
try {
|
||||
@@ -50,12 +54,13 @@ export async function transcribeSpeech(
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: getSpeechToTextModel(input.model).id,
|
||||
model: model.id,
|
||||
input_audio: {
|
||||
data: input.data,
|
||||
format: input.format,
|
||||
},
|
||||
...(input.language ? { language: input.language } : {}),
|
||||
...(prompt ? { prompt } : {}),
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ const TSX_FILES = [
|
||||
path.join(ROOT, "webview-ui/agent-manager/DiffEndMarker.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/FileTree.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/review-annotations.ts"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/review-annotation-speech.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/MultiModelSelector.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/ApplyDialog.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/WorktreeItem.tsx"),
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { formatTranscript } from "../../src/kilo-provider/export-transcript"
|
||||
|
||||
describe("formatTranscript", () => {
|
||||
it("formats complete Markdown session exports and skips hidden text", () => {
|
||||
const text = formatTranscript(
|
||||
{
|
||||
id: "ses_123456789",
|
||||
slug: "export-test",
|
||||
projectID: "project",
|
||||
directory: "/repo",
|
||||
title: "Export test",
|
||||
version: "1",
|
||||
time: { created: 1_000, updated: 2_000 },
|
||||
} as never,
|
||||
[
|
||||
{
|
||||
info: { role: "user" },
|
||||
parts: [
|
||||
{ type: "text", text: "Please export this." },
|
||||
{ type: "text", text: "hidden", synthetic: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
info: { role: "assistant" },
|
||||
parts: [
|
||||
{ type: "tool", tool: "read" },
|
||||
{ type: "text", text: "Saved as Markdown." },
|
||||
],
|
||||
},
|
||||
] as never,
|
||||
)
|
||||
|
||||
expect(text).toContain("# Export test")
|
||||
expect(text).toContain("**Session ID:** ses_123456789")
|
||||
expect(text).toContain("## User\n\nPlease export this.")
|
||||
expect(text).toContain("## Assistant\n\n**Tool: read**\n\nSaved as Markdown.")
|
||||
expect(text).not.toContain("hidden")
|
||||
})
|
||||
|
||||
it("keeps an empty session export readable", () => {
|
||||
const text = formatTranscript(
|
||||
{
|
||||
id: "ses_empty",
|
||||
slug: "empty",
|
||||
projectID: "project",
|
||||
directory: "/repo",
|
||||
title: "Empty",
|
||||
version: "1",
|
||||
time: { created: 1_000, updated: 2_000 },
|
||||
} as never,
|
||||
[],
|
||||
)
|
||||
|
||||
expect(text).toContain("# Empty")
|
||||
expect(text).toEndWith("---\n\n")
|
||||
})
|
||||
})
|
||||
@@ -93,6 +93,17 @@ describe("resolveNewSessionDirectory", () => {
|
||||
expect(dir).toBe("/repo/.kilo/worktrees/feature")
|
||||
})
|
||||
|
||||
it("uses explicit Agent Manager worktree context after the active session was cleared", () => {
|
||||
const dir = resolveNewSessionDirectory({
|
||||
agentManagerContext: "wt_feature",
|
||||
contextDirectory: "/repo/.kilo/worktrees/feature",
|
||||
sessionDirectories: new Map(),
|
||||
workspaceDirectory: "/repo",
|
||||
})
|
||||
|
||||
expect(dir).toBe("/repo/.kilo/worktrees/feature")
|
||||
})
|
||||
|
||||
it("creates local Agent Manager sessions in the workspace root after a worktree was selected", () => {
|
||||
const dir = resolveNewSessionDirectory({
|
||||
contextSessionID: "ses_worktree",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { isAnnotationMutation } from "../../webview-ui/agent-manager/markdown-annotation-mutation"
|
||||
|
||||
function mutation(target: Node): Pick<MutationRecord, "target"> {
|
||||
return { target }
|
||||
}
|
||||
|
||||
describe("isAnnotationMutation", () => {
|
||||
it("matches nested annotation DOM mutations", () => {
|
||||
const target = { closest: () => ({}) } as unknown as Node
|
||||
expect(isAnnotationMutation(mutation(target))).toBe(true)
|
||||
})
|
||||
|
||||
it("keeps external Markdown DOM mutations observable", () => {
|
||||
const markdown = { closest: () => null } as unknown as Node
|
||||
const plain = {} as Node
|
||||
expect(isAnnotationMutation(mutation(markdown))).toBe(false)
|
||||
expect(isAnnotationMutation(mutation(plain))).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Tests for plan_exit webview helpers:
|
||||
* - planDisplayPath: relative/absolute path display logic
|
||||
* - plan_exit renderer uses openFile, not openDiff
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { planDisplayPath } from "../../webview-ui/src/utils/plan-path"
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
describe("planDisplayPath", () => {
|
||||
it("returns a relative path unchanged", () => {
|
||||
expect(planDisplayPath(".kilo/plans/my-plan.md", "/repo")).toBe(".kilo/plans/my-plan.md")
|
||||
})
|
||||
|
||||
it("returns absolute path inside repo as repo-relative", () => {
|
||||
expect(planDisplayPath("/repo/.kilo/plans/my-plan.md", "/repo")).toBe(".kilo/plans/my-plan.md")
|
||||
})
|
||||
|
||||
it("returns absolute path inside repo with trailing slash on root as repo-relative", () => {
|
||||
expect(planDisplayPath("/repo/.kilo/plans/my-plan.md", "/repo/")).toBe(".kilo/plans/my-plan.md")
|
||||
})
|
||||
|
||||
it("returns absolute path outside repo unchanged", () => {
|
||||
expect(planDisplayPath("/other/path/plan.md", "/repo")).toBe("/other/path/plan.md")
|
||||
})
|
||||
|
||||
it("handles Windows absolute paths inside root", () => {
|
||||
expect(planDisplayPath("C:\\repo\\.kilo\\plans\\plan.md", "C:\\repo")).toBe(".kilo\\plans\\plan.md")
|
||||
})
|
||||
|
||||
it("handles Windows absolute paths outside root", () => {
|
||||
expect(planDisplayPath("D:\\other\\plan.md", "C:\\repo")).toBe("D:\\other\\plan.md")
|
||||
})
|
||||
|
||||
it("returns empty string unchanged", () => {
|
||||
expect(planDisplayPath("", "/repo")).toBe("")
|
||||
})
|
||||
|
||||
it("path equal to root returns original", () => {
|
||||
// Edge: plan path IS the root directory itself — fall back to original
|
||||
expect(planDisplayPath("/repo", "/repo")).toBe("/repo")
|
||||
})
|
||||
})
|
||||
|
||||
describe("plan_exit renderer uses openFile not openDiff (source)", () => {
|
||||
const ROOT = path.resolve(import.meta.dir, "../..")
|
||||
const FILE = path.join(ROOT, "webview-ui/src/components/chat/AssistantMessage.tsx")
|
||||
const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx")
|
||||
const src = fs.readFileSync(FILE, "utf-8")
|
||||
const turnSrc = fs.readFileSync(TURN_FILE, "utf-8")
|
||||
|
||||
it("PlanExitCard calls data.openFile", () => {
|
||||
expect(src).toContain("data.openFile")
|
||||
})
|
||||
|
||||
it("uses a safe inert anchor href", () => {
|
||||
expect(src).toContain('href="#"')
|
||||
expect(src).not.toContain("href={display()}")
|
||||
})
|
||||
|
||||
it("always uses the generic ready label", () => {
|
||||
expect(src).toContain('language.t("plan.exit.ready")')
|
||||
expect(src).not.toContain('language.t("plan.exit.readyUpdated")')
|
||||
expect(src).not.toContain('language.t("plan.exit.readyNew")')
|
||||
})
|
||||
|
||||
it("does not infer status from tool history", () => {
|
||||
expect(src).not.toContain("function inferPlanStatus")
|
||||
expect(src).not.toContain("function patchUpdatedPlan")
|
||||
expect(src).not.toContain("function toolTouchesPlan")
|
||||
expect(src).not.toContain("toolDeletions")
|
||||
expect(src).not.toContain("readyUpdated")
|
||||
expect(src).not.toContain("readyNew")
|
||||
expect(src).not.toContain("Object.values(data.store.part ?? {}).flat()")
|
||||
expect(src).not.toContain("[...props.parts, ...all()]")
|
||||
expect(src).not.toContain("turnParts")
|
||||
expect(turnSrc).not.toContain("assistantMessages().flatMap")
|
||||
expect(turnSrc).not.toContain("turnParts={assistantParts()}")
|
||||
})
|
||||
|
||||
it("does not depend on opencode-provided plan status metadata", () => {
|
||||
expect(src).not.toContain('meta.status === "updated" || meta.status === "new"')
|
||||
expect(src).not.toContain("meta.status")
|
||||
})
|
||||
|
||||
it("PlanExitCard does not call openDiffVirtual", () => {
|
||||
// Extract just the PlanExitCard function body to scope the assertion
|
||||
const start = src.indexOf("function PlanExitCard")
|
||||
const end = src.indexOf("\nfunction ", start + 1)
|
||||
const block = end === -1 ? src.slice(start) : src.slice(start, end)
|
||||
expect(block).not.toContain("openDiffVirtual")
|
||||
expect(block).not.toContain("openDiff")
|
||||
})
|
||||
|
||||
it("plan_exit tool is handled before generic Part renderer", () => {
|
||||
const planExitIdx = src.indexOf("planExit()")
|
||||
// <Part may be followed by newline or space
|
||||
const partIdx = src.search(/<Part[\s\n]/)
|
||||
expect(planExitIdx).toBeGreaterThan(0)
|
||||
expect(partIdx).toBeGreaterThan(0)
|
||||
expect(planExitIdx).toBeLessThan(partIdx)
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,11 @@ import {
|
||||
type ReviewComment,
|
||||
} from "../../webview-ui/agent-manager/review-comments"
|
||||
import { markdownCommentBlocks } from "../../webview-ui/agent-manager/markdown-comment-ranges"
|
||||
import {
|
||||
reviewAnnotationSpeechKey,
|
||||
reviewDraftSpeechKey,
|
||||
reviewEditSpeechKey,
|
||||
} from "../../webview-ui/agent-manager/review-annotations"
|
||||
import type { WorktreeFileDiff } from "../../webview-ui/src/types/messages"
|
||||
|
||||
function diff(file: string, before: string, after: string): WorktreeFileDiff {
|
||||
@@ -236,6 +241,53 @@ describe("markdownCommentBlocks", () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ── review annotation speech keys ───────────────────────────────────────────
|
||||
|
||||
describe("review annotation speech keys", () => {
|
||||
it("keeps draft keys scoped to the selected review location", () => {
|
||||
expect(reviewDraftSpeechKey({ file: "src/a.ts", side: "additions", line: 4 })).toBe("draft:src/a.ts:additions:4:4")
|
||||
expect(reviewDraftSpeechKey({ file: "src/a.ts", side: "deletions", line: 4, endLine: 8 })).toBe(
|
||||
"draft:src/a.ts:deletions:4:8",
|
||||
)
|
||||
})
|
||||
|
||||
it("keeps edit keys scoped to the review comment id", () => {
|
||||
expect(reviewEditSpeechKey("c-123")).toBe("edit:c-123")
|
||||
})
|
||||
|
||||
it("only exposes annotation speech keys for active draft and edit composers", () => {
|
||||
const current = comment({ file: "src/a.ts", line: 7 })
|
||||
expect(
|
||||
reviewAnnotationSpeechKey({
|
||||
type: "draft",
|
||||
comment: null,
|
||||
file: "src/a.ts",
|
||||
side: "additions",
|
||||
line: 7,
|
||||
}),
|
||||
).toBe("draft:src/a.ts:additions:7:7")
|
||||
expect(
|
||||
reviewAnnotationSpeechKey({
|
||||
type: "comment",
|
||||
comment: current,
|
||||
file: current.file,
|
||||
side: current.side,
|
||||
line: current.line,
|
||||
editing: true,
|
||||
}),
|
||||
).toBe(`edit:${current.id}`)
|
||||
expect(
|
||||
reviewAnnotationSpeechKey({
|
||||
type: "comment",
|
||||
comment: current,
|
||||
file: current.file,
|
||||
side: current.side,
|
||||
line: current.line,
|
||||
}),
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
// ── getDirectory / getFilename ──────────────────────────────────────────────
|
||||
|
||||
describe("getDirectory", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { parseDshowAudioDevices } from "../../src/speech-to-text/capture"
|
||||
import { cleanOutput, parseDshowAudioDevices } from "../../src/speech-to-text/capture"
|
||||
|
||||
describe("parseDshowAudioDevices", () => {
|
||||
it("extracts Windows dshow audio device names", () => {
|
||||
@@ -20,3 +20,20 @@ describe("parseDshowAudioDevices", () => {
|
||||
expect(parseDshowAudioDevices(raw)).toEqual(["Microphone"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("cleanOutput", () => {
|
||||
it("removes ffmpeg build noise from capture errors", () => {
|
||||
const raw = `
|
||||
ffmpeg version 4.2.7 Copyright (c) 2000-2022 the FFmpeg developers
|
||||
built with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.2)
|
||||
configuration: --enable-libopus --enable-libx264
|
||||
libavutil 56. 31.100 / 56. 31.100
|
||||
ALSA lib ../../../src/pcm/pcm.c:2477:(snd_pcm_open_conf) Unknown field libs
|
||||
default: Input/output error
|
||||
`
|
||||
|
||||
expect(cleanOutput(raw)).toBe(
|
||||
"ALSA lib ../../../src/pcm/pcm.c:2477:(snd_pcm_open_conf) Unknown field libs\ndefault: Input/output error",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { describe, expect, it, mock } from "bun:test"
|
||||
import { createRoot } from "solid-js"
|
||||
import type { ExtensionMessage, WebviewMessage } from "../../webview-ui/src/types/messages"
|
||||
|
||||
mock.module("@kilocode/kilo-ui/toast", () => ({
|
||||
showToast: () => undefined,
|
||||
}))
|
||||
|
||||
const { useSpeechToText } = await import("../../webview-ui/src/components/speech-to-text/useSpeechToText")
|
||||
|
||||
function setup() {
|
||||
const sent: WebviewMessage[] = []
|
||||
let handler: ((message: ExtensionMessage) => void) | undefined
|
||||
|
||||
const root = createRoot((dispose) => ({
|
||||
dispose,
|
||||
speech: useSpeechToText(
|
||||
{
|
||||
postMessage: (message) => sent.push(message),
|
||||
onMessage: (next) => {
|
||||
handler = next
|
||||
return () => {
|
||||
handler = undefined
|
||||
}
|
||||
},
|
||||
},
|
||||
{ profileData: () => ({}), goToLogin: () => {} },
|
||||
{ t: (key) => key },
|
||||
),
|
||||
}))
|
||||
|
||||
const fire = (message: ExtensionMessage) => handler?.(message)
|
||||
return { ...root, fire, sent }
|
||||
}
|
||||
|
||||
describe("useSpeechToText", () => {
|
||||
it("runs the stop completion after inserting a transcript", () => {
|
||||
const ctx = setup()
|
||||
const text: string[] = []
|
||||
let done = 0
|
||||
|
||||
ctx.speech.start({ model: "scribe", insert: (value) => text.push(value) })
|
||||
const start = ctx.sent[0]
|
||||
if (start?.type !== "speechToTextStart") throw new Error("speech start message missing")
|
||||
|
||||
ctx.speech.stop({ done: () => done++ })
|
||||
ctx.fire({ type: "speechToTextResult", requestId: start.requestId, text: "Recorded prompt" })
|
||||
|
||||
expect(text).toEqual(["Recorded prompt"])
|
||||
expect(done).toBe(1)
|
||||
expect(ctx.speech.state()).toBe("idle")
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("drops the stop completion when transcription is cancelled", () => {
|
||||
const ctx = setup()
|
||||
let done = 0
|
||||
|
||||
ctx.speech.start({ model: "scribe", insert: () => {} })
|
||||
const start = ctx.sent[0]
|
||||
if (start?.type !== "speechToTextStart") throw new Error("speech start message missing")
|
||||
|
||||
ctx.speech.stop({ done: () => done++ })
|
||||
ctx.speech.cancel()
|
||||
ctx.fire({ type: "speechToTextResult", requestId: start.requestId, text: "Ignored prompt" })
|
||||
|
||||
expect(done).toBe(0)
|
||||
expect(ctx.speech.state()).toBe("idle")
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("drops the stop completion when the send context changes", () => {
|
||||
const ctx = setup()
|
||||
const text: string[] = []
|
||||
let done = 0
|
||||
|
||||
ctx.speech.start({ model: "scribe", insert: (value) => text.push(value) })
|
||||
const start = ctx.sent[0]
|
||||
if (start?.type !== "speechToTextStart") throw new Error("speech start message missing")
|
||||
|
||||
ctx.speech.stop({ done: () => done++, ready: () => false })
|
||||
ctx.fire({ type: "speechToTextResult", requestId: start.requestId, text: "Keep as draft" })
|
||||
|
||||
expect(text).toEqual(["Keep as draft"])
|
||||
expect(done).toBe(0)
|
||||
expect(ctx.speech.state()).toBe("idle")
|
||||
ctx.dispose()
|
||||
})
|
||||
})
|
||||
@@ -130,6 +130,7 @@ import { createMarkdownRender } from "./review-preferences"
|
||||
import { createSidebarCollapse } from "./sidebar-collapse"
|
||||
import { SidebarToggleButton } from "./SidebarToggleButton"
|
||||
import { setTabWidths } from "./tab-widths"
|
||||
import { buildShortcutCategories } from "./shortcuts"
|
||||
import "./agent-manager.css"
|
||||
import "./agent-manager-review.css"
|
||||
const REVIEW_TAB_ID = "review"
|
||||
@@ -185,76 +186,6 @@ const defaultBindings: Record<string, string> = {
|
||||
),
|
||||
}
|
||||
|
||||
/** Shortcut category definition for the keyboard shortcuts dialog */
|
||||
interface ShortcutEntry {
|
||||
label: string
|
||||
binding: string
|
||||
}
|
||||
|
||||
interface ShortcutCategory {
|
||||
title: string
|
||||
shortcuts: ShortcutEntry[]
|
||||
}
|
||||
|
||||
/** Build the categorized list of keyboard shortcuts from the current bindings */
|
||||
function buildShortcutCategories(
|
||||
bindings: Record<string, string>,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): ShortcutCategory[] {
|
||||
return [
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.quickSwitch"),
|
||||
shortcuts: [
|
||||
{
|
||||
label: t("agentManager.shortcuts.jumpToItem"),
|
||||
binding: (() => {
|
||||
const first = bindings.jumpTo1 ?? ""
|
||||
const prefix = first.replace(/\d+$/, "")
|
||||
return prefix ? `${prefix}1-9` : ""
|
||||
})(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.sidebar"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousItem"), binding: bindings.previousSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextItem"), binding: bindings.nextSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newWorktree"), binding: bindings.newWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.advancedWorktree"), binding: bindings.advancedWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.deleteWorktree"), binding: bindings.closeWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.openWorktree"), binding: bindings.openWorktree ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.tabs"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousTab"), binding: bindings.previousTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextTab"), binding: bindings.nextTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newTab"), binding: bindings.newTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.closeTab"), binding: bindings.closeTab ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.terminal"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.toggleTerminal"), binding: bindings.showTerminal ?? "" },
|
||||
{ label: t("agentManager.shortcuts.runScript"), binding: bindings.runScript ?? "" },
|
||||
{ label: t("agentManager.shortcuts.toggleDiff"), binding: bindings.toggleDiff ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.global"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.openAgentManager"), binding: bindings.agentManagerOpen ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cycleAgentMode"), binding: bindings.cycleAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cyclePreviousAgentMode"), binding: bindings.cyclePreviousAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.showShortcuts"), binding: bindings.showShortcuts ?? "" },
|
||||
].filter((s) => s.binding),
|
||||
},
|
||||
].filter((c) => c.shortcuts.length > 0)
|
||||
}
|
||||
|
||||
import { parseBindingTokens } from "./keybind-tokens"
|
||||
|
||||
const AgentManagerContent: Component = () => {
|
||||
@@ -1003,6 +934,27 @@ const AgentManagerContent: Component = () => {
|
||||
setReviewActive(remembered === REVIEW_TAB_ID && reviewOpenByContext()[worktreeId] === true)
|
||||
}
|
||||
|
||||
const addSessionToCurrentWorktree = (sid: string) => {
|
||||
const sel = selection()
|
||||
if (!sel || sel === LOCAL) return false
|
||||
const current = managedSessions().find((entry) => entry.id === sid)
|
||||
if (current?.worktreeId) return focusManagedSession(current.worktreeId, sid)
|
||||
saveTabMemory()
|
||||
setHistory(false)
|
||||
setReviewActive(false)
|
||||
appendToTabOrder(sel, sid)
|
||||
evictLocal(sid)
|
||||
vscode.postMessage({ type: "agentManager.addSessionToWorktree", worktreeId: sel, sessionId: sid })
|
||||
return true
|
||||
}
|
||||
|
||||
const focusManagedSession = (worktreeId: string, sid: string) => {
|
||||
selectWorktree(worktreeId)
|
||||
setHistory(false)
|
||||
session.selectSession(sid)
|
||||
return true
|
||||
}
|
||||
|
||||
const cycleAgent = (direction: 1 | -1) => {
|
||||
const available = session.agents().filter((a) => a.mode !== "subagent" && !a.hidden)
|
||||
if (available.length <= 1) return
|
||||
@@ -1220,6 +1172,7 @@ const AgentManagerContent: Component = () => {
|
||||
saveTabMemory()
|
||||
appendToTabOrder(ev.worktreeId, ev.sessionId)
|
||||
setSelection(ev.worktreeId)
|
||||
evictLocal(ev.sessionId)
|
||||
drafts.apply(ev.worktreeId, ev.sessionId)
|
||||
session.selectSession(ev.sessionId)
|
||||
}
|
||||
@@ -2988,6 +2941,7 @@ const AgentManagerContent: Component = () => {
|
||||
<Show when={history()}>
|
||||
<HistoryView
|
||||
onSelectSession={(id) => {
|
||||
if (addSessionToCurrentWorktree(id)) return
|
||||
setHistory(false)
|
||||
if (localSessionIDs().includes(id)) {
|
||||
saveTabMemory()
|
||||
@@ -3021,6 +2975,7 @@ const AgentManagerContent: Component = () => {
|
||||
<div class="am-chat-wrapper">
|
||||
<ChatView
|
||||
onSelectSession={(id) => {
|
||||
if (addSessionToCurrentWorktree(id)) return
|
||||
if (localSessionIDs().includes(id)) {
|
||||
session.selectSession(id)
|
||||
if (selection() === null) setSelection(LOCAL)
|
||||
|
||||
@@ -14,13 +14,22 @@ import type { DiffLineAnnotation, AnnotationSide, SelectedLineRange } from "@pie
|
||||
import type { WorktreeFileDiff } from "../src/types/messages"
|
||||
import { KILO_FILE_PATH_MIME } from "../src/utils/path-mentions"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { useVSCode } from "../src/context/vscode"
|
||||
import { useServer } from "../src/context/server"
|
||||
import { useProvider } from "../src/context/provider"
|
||||
import { useConfig } from "../src/context/config"
|
||||
import { canUseSpeechToText, selectedSpeechToTextModel } from "../src/components/speech-to-text/availability"
|
||||
import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToText"
|
||||
import { getDirectory, getFilename, lineCount, sanitizeReviewComments, type ReviewComment } from "./review-comments"
|
||||
import {
|
||||
buildFileAnnotations,
|
||||
buildReviewAnnotation,
|
||||
reviewDraftSpeechKey,
|
||||
reviewEditSpeechKey,
|
||||
type AnnotationLabels,
|
||||
type AnnotationMeta,
|
||||
} from "./review-annotations"
|
||||
import { createReviewAnnotationSpeechRenderer } from "./review-annotation-speech"
|
||||
import {
|
||||
LONG_DIFF_MARKER_FILE_COUNT,
|
||||
allOpenFiles,
|
||||
@@ -59,6 +68,13 @@ interface DiffPanelProps {
|
||||
|
||||
export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
const vscode = useVSCode()
|
||||
const server = useServer()
|
||||
const provider = useProvider()
|
||||
const { config, settings } = useConfig()
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(settings(), config(), provider.connected(), server.profileData())
|
||||
const speechModel = () => selectedSpeechToTextModel(settings())
|
||||
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
|
||||
const sendAllKeybind = () =>
|
||||
isMac ? t("agentManager.review.sendAllShortcut.mac") : t("agentManager.review.sendAllShortcut.other")
|
||||
@@ -78,6 +94,21 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
null,
|
||||
)
|
||||
const [editing, setEditing] = createSignal<string | null>(null)
|
||||
const speechKeys = createMemo(() => {
|
||||
const keys = new Set<string>()
|
||||
const current = draft()
|
||||
const edit = editing()
|
||||
if (current) keys.add(reviewDraftSpeechKey(current))
|
||||
if (edit) keys.add(reviewEditSpeechKey(edit))
|
||||
return keys
|
||||
})
|
||||
const reviewSpeech = createReviewAnnotationSpeechRenderer({
|
||||
speech,
|
||||
enabled: canUseSpeech,
|
||||
model: speechModel,
|
||||
label: t,
|
||||
keys: speechKeys,
|
||||
})
|
||||
let nextId = 0
|
||||
// Tracks the session key for which initial open state has already run. When the
|
||||
// key changes (different worktree) we expand reviewable files. Within the same key,
|
||||
@@ -307,6 +338,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
cancelDraft,
|
||||
labels: labels(),
|
||||
activeTerminalId: props.activeTerminalId,
|
||||
speech: reviewSpeech,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,24 @@ import type { DiffLineAnnotation, AnnotationSide, SelectedLineRange } from "@pie
|
||||
import type { WorktreeFileDiff } from "../src/types/messages"
|
||||
import { KILO_FILE_PATH_MIME } from "../src/utils/path-mentions"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { useVSCode } from "../src/context/vscode"
|
||||
import { useServer } from "../src/context/server"
|
||||
import { useProvider } from "../src/context/provider"
|
||||
import { useConfig } from "../src/context/config"
|
||||
import { canUseSpeechToText, selectedSpeechToTextModel } from "../src/components/speech-to-text/availability"
|
||||
import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToText"
|
||||
import { FileTree } from "./FileTree"
|
||||
import { treeOrder } from "./file-tree-utils"
|
||||
import { getDirectory, getFilename, lineCount, sanitizeReviewComments, type ReviewComment } from "./review-comments"
|
||||
import {
|
||||
buildFileAnnotations,
|
||||
buildReviewAnnotation,
|
||||
reviewDraftSpeechKey,
|
||||
reviewEditSpeechKey,
|
||||
type AnnotationLabels,
|
||||
type AnnotationMeta,
|
||||
} from "./review-annotations"
|
||||
import { createReviewAnnotationSpeechRenderer } from "./review-annotation-speech"
|
||||
import {
|
||||
LONG_DIFF_MARKER_FILE_COUNT,
|
||||
allOpenFiles,
|
||||
@@ -69,6 +78,13 @@ interface FullScreenDiffViewProps {
|
||||
|
||||
export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
const vscode = useVSCode()
|
||||
const server = useServer()
|
||||
const provider = useProvider()
|
||||
const { config, settings } = useConfig()
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(settings(), config(), provider.connected(), server.profileData())
|
||||
const speechModel = () => selectedSpeechToTextModel(settings())
|
||||
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
|
||||
const sendAllKeybind = () =>
|
||||
isMac ? t("agentManager.review.sendAllShortcut.mac") : t("agentManager.review.sendAllShortcut.other")
|
||||
@@ -88,6 +104,21 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
null,
|
||||
)
|
||||
const [editing, setEditing] = createSignal<string | null>(null)
|
||||
const speechKeys = createMemo(() => {
|
||||
const keys = new Set<string>()
|
||||
const current = draft()
|
||||
const edit = editing()
|
||||
if (current) keys.add(reviewDraftSpeechKey(current))
|
||||
if (edit) keys.add(reviewEditSpeechKey(edit))
|
||||
return keys
|
||||
})
|
||||
const reviewSpeech = createReviewAnnotationSpeechRenderer({
|
||||
speech,
|
||||
enabled: canUseSpeech,
|
||||
model: speechModel,
|
||||
label: t,
|
||||
keys: speechKeys,
|
||||
})
|
||||
const [activeFile, setActiveFile] = createSignal<string | null>(null)
|
||||
const [treeWidth, setTreeWidth] = createSignal(240)
|
||||
let nextId = 0
|
||||
@@ -322,6 +353,7 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
cancelDraft,
|
||||
labels: labels(),
|
||||
activeTerminalId: props.activeTerminalId,
|
||||
speech: reviewSpeech,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type Component, createEffect, createMemo, onCleanup } from "solid-js"
|
||||
import type { AnnotationSide, DiffLineAnnotation, SelectedLineRange } from "@pierre/diffs"
|
||||
import { markdownCommentBlocks, type MarkdownRange } from "./markdown-comment-ranges"
|
||||
import type { AnnotationMeta } from "./review-annotations"
|
||||
import { annotationSelector, isAnnotationMutation } from "./markdown-annotation-mutation"
|
||||
|
||||
type Insert = "after" | "list" | "table"
|
||||
|
||||
@@ -88,11 +89,11 @@ function matches(annotation: DiffLineAnnotation<AnnotationMeta>, anchor: Anchor,
|
||||
return true
|
||||
}
|
||||
|
||||
const selector = annotationSelector()
|
||||
|
||||
function removeInserted(root: HTMLElement, layer: HTMLElement): void {
|
||||
layer.replaceChildren()
|
||||
root
|
||||
.querySelectorAll(".am-markdown-inline-annotations, .am-markdown-list-annotation, .am-markdown-table-annotation")
|
||||
.forEach((node) => node.remove())
|
||||
root.querySelectorAll(selector).forEach((node) => node.remove())
|
||||
}
|
||||
|
||||
function insertHost(anchor: Anchor, host: HTMLElement): void {
|
||||
@@ -206,7 +207,10 @@ export const MarkdownAnnotationLayer: Component<MarkdownAnnotationLayerProps> =
|
||||
const root = props.root()
|
||||
if (!root) return
|
||||
observer?.disconnect()
|
||||
observer = new MutationObserver(schedule)
|
||||
observer = new MutationObserver((mutations) => {
|
||||
if (mutations.every(isAnnotationMutation)) return
|
||||
schedule()
|
||||
})
|
||||
observer.observe(root, { childList: true, subtree: true })
|
||||
})
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user