Merge branch 'main' into fix/plan-mode-mkdir-recursive-onedrive-9755

This commit is contained in:
Marian Alexandru Alecu
2026-05-06 14:50:22 +03:00
committed by GitHub
1875 changed files with 35606 additions and 90634 deletions
-6
View File
@@ -1,6 +0,0 @@
---
"kilo-code": minor
"@kilocode/cli": minor
---
Support starting Agent Manager local sessions and worktree sessions from an experimental agent tool.
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Dispose autocomplete editor listeners when inline completions are disabled.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve the JetBrains model picker with sections, favorites, and smart filtering.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Center the sidebar prompt toolbar on wider sidebars and hide unavailable toolbar actions.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Center welcome notifications in wide VS Code views.
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Avoid repeated command approval prompts when multiple sessions request the same saved command permission, without widening bash permission matching.
@@ -1,6 +0,0 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Scope the custom commit message prompt to the current project. Setting it in the VS Code settings now writes to the workspace's `kilo.json` so different repositories can have different conventions, instead of silently applying globally. Also fixes the project-level config update endpoint, which previously wrote to a file that wasn't loaded.
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---
Preserve the selected thinking level after compacting a session.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Scale JetBrains chat transcript fonts from the editor font settings.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Keep reasoning blocks expanded by default and add a setting to auto-collapse them after completion.
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Honor allowed read-only external-directory access to Kilo config paths without repeated permission prompts.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Keep the JetBrains model picker at the same row position when models are favorited or unfavorited.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Use a GPT-5.5-specific coding prompt that improves autonomous task handling while keeping older Codex generations on their existing prompt.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Match VS Code-style reasoning and tool run rendering in JetBrains chat.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Improve the JetBrains mode picker with descriptions, deprecated badges, and native IntelliJ selection behavior.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Show a scroll-to-bottom button when JetBrains chat sessions are scrolled away from the latest message.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support rebinding the JetBrains prompt Send action in the IntelliJ keymap.
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Control JetBrains chat transcript spacing from the session layout.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support stopping active JetBrains sessions from the prompt action button.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Preserve Agent Manager local sessions across panel restarts when session refreshes complete out of order.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Size the JetBrains model picker from its rendered model rows instead of fixed popup dimensions.
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Update regular Kilo editor tab titles to follow the current session title.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": patch
---
Support configuring custom agent tool permissions from the VS Code agent editor.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Support reasoning effort selection and resetting JetBrains chat model overrides to the default model.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Align checked and unchecked reasoning picker rows in the JetBrains plugin.
-5
View File
@@ -1,5 +0,0 @@
---
"@kilocode/cli": patch
---
Keep Remote status visible in the TUI while remote control is connecting.
-6
View File
@@ -1,6 +0,0 @@
---
"kilo-code": patch
"@kilocode/cli": patch
---
Stop emitting `ai.*` and `gen_ai.*` OpenTelemetry spans from AI SDK calls, and remove the PostHog bridge that forwarded them. Tool/session/indexing telemetry is unchanged.
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Preserve JetBrains chat scroll position when reviewing earlier session output.
-5
View File
@@ -1,5 +0,0 @@
---
"kilo-code": minor
---
Add a Display setting to collapse terminal command blocks by default instead of keeping them expanded.
@@ -39,6 +39,15 @@ runs:
- name: Clear checkout auth
run: |
git config --local --unset-all http.https://github.com/.extraheader || true
# kilocode_change start
keys="$(git config --local --name-only --get-regexp '^includeIf\.gitdir:' || true)"
for key in $keys; do
path="$(git config --local --get "$key" || true)"
case "$path" in
*git-credentials-*.config) git config --local --unset-all "$key" || true ;;
esac
done
# kilocode_change end
shell: bash
- name: Configure git remote
+2 -1
View File
@@ -14,8 +14,9 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
persist-credentials: false # kilocode_change
fetch-depth: 0
- name: Setup Bun
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: github.repository == 'Kilo-Org/kilocode'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
@@ -6,9 +6,6 @@ on:
- ".github/**"
- "github/**"
- "sdks/vscode/**"
- "packages/app/**"
- "packages/desktop/**"
- "packages/desktop-electron/**"
- "packages/extensions/**"
- "packages/opencode/**"
- "packages/script/**"
@@ -24,7 +21,7 @@ jobs:
if: github.repository == 'Kilo-Org/kilocode'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
@@ -40,3 +37,8 @@ jobs:
else
echo "No PR base SHA available (workflow_dispatch without PR context) — skipping."
fi
# kilocode_change start
- name: Check workflow allowlist
run: bun run script/check-workflows.ts
# kilocode_change end
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
- uses: oven-sh/setup-bun@v2
with:
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
OWNER: ${{ github.repository_owner }}
# kilocode_change end
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
- uses: ./.github/actions/setup-bun
@@ -13,7 +13,6 @@ permissions:
jobs:
close-non-compliant:
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: ubuntu-latest
steps:
- name: Close non-compliant issues and PRs after 2 hours
@@ -8,9 +8,6 @@ on:
jobs:
daily-recap:
# kilocode_change start - disabled for kilo-cli fork (OpenCode Discord integration)
if: false
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
@@ -24,7 +21,7 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
run: curl -fsSL https://kilo.ai/install | bash
- name: Generate daily issues recap
id: recap
@@ -46,7 +43,7 @@ jobs:
# Get today's date range
TODAY=$(date -u +%Y-%m-%d)
opencode run -m kilo/anthropic/claude-sonnet-4.5 "Generate a daily issues recap for the OpenCode repository.
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the Kilo repository.
TODAY'S DATE: ${TODAY}
@@ -8,9 +8,6 @@ on:
jobs:
pr-recap:
# kilocode_change start - disabled for kilo-cli fork (OpenCode Discord integration)
if: false
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
@@ -24,7 +21,7 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
run: curl -fsSL https://kilo.ai/install | bash
- name: Generate daily PR recap
id: recap
@@ -45,7 +42,7 @@ jobs:
run: |
TODAY=$(date -u +%Y-%m-%d)
opencode run -m kilo/anthropic/claude-sonnet-4.5 "Generate a daily PR activity recap for the OpenCode repository.
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the Kilo repository.
TODAY'S DATE: ${TODAY}
@@ -5,20 +5,7 @@ on:
types: [opened]
jobs:
# kilocode_change start
check-author:
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
uses: ./.github/workflows/check-org-member.yml
with:
username: ${{ github.event.pull_request.user.login }}
secrets:
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
check-duplicates:
needs: check-author
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
@@ -48,10 +35,9 @@ jobs:
if: steps.team-check.outputs.is_team != 'true'
run: bun install
# kilocode_change start
- name: Setup Kilo
uses: ./.github/actions/setup-kilo
# kilocode_change end
- name: Install opencode
if: steps.team-check.outputs.is_team != 'true'
run: curl -fsSL https://kilo.ai/install | bash
- name: Build prompt
if: steps.team-check.outputs.is_team != 'true'
@@ -73,10 +59,7 @@ jobs:
- name: Check for duplicate PRs
if: steps.team-check.outputs.is_team != 'true'
env:
# kilocode_change start
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
# kilocode_change end
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
@@ -89,8 +72,7 @@ jobs:
fi
add-contributor-label:
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
@@ -5,22 +5,10 @@ on:
types: [opened, edited, synchronize]
jobs:
# kilocode_change start
check-author:
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
uses: ./.github/workflows/check-org-member.yml
with:
username: ${{ github.event.pull_request.user.login }}
secrets:
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
check-standards:
needs: check-author
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
# kilocode_change end
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check PR standards
@@ -28,6 +16,30 @@ jobs:
with:
script: |
const pr = context.payload.pull_request;
const login = pr.user.login;
// Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
const cutoff = new Date('2026-02-19T00:00:00Z');
const prCreated = new Date(pr.created_at);
if (prCreated < cutoff) {
console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
return;
}
// Check if author is a team member or bot
if (login === 'opencode-agent[bot]') return;
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/TEAM_MEMBERS',
ref: 'dev'
});
const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
if (members.includes(login)) {
console.log(`Skipping: ${login} is a team member`);
return;
}
const title = pr.title;
async function addLabel(label) {
@@ -59,10 +71,10 @@ jobs:
repo: context.repo.repo,
issue_number: pr.number
});
const existing = comments.find(c => c.body.includes(markerText));
if (existing) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -88,9 +100,9 @@ jobs:
- \`refactor:\` or \`refactor(scope):\` code refactoring
- \`test:\` or \`test(scope):\` adding or updating tests
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`kilo\`).
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).
See [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md#pr-titles) for details.`);
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
return;
}
@@ -133,7 +145,7 @@ jobs:
1. Open an issue describing the bug/feature (if one doesn't exist)
2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description
See [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md#issue-first-policy) for details.`);
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
return;
}
@@ -141,7 +153,6 @@ jobs:
console.log('PR meets all standards');
check-compliance:
if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo
runs-on: ubuntu-latest
permissions:
contents: read
@@ -49,13 +49,13 @@ jobs:
- name: Check PR guidelines compliance
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KILO_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
PR_TITLE: ${{ steps.pr-details.outputs.title }}
run: |
PR_BODY=$(jq -r .body pr_data.json)
opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}'
opencode run -m opencode/gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}'
<pr-number>
${{ steps.pr-number.outputs.number }}
@@ -25,7 +25,6 @@ concurrency:
jobs:
build:
if: false
name: storybook build
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
- name: Setup Bun
uses: ./.github/actions/setup-bun
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository == 'Kilo-Org/kilocode'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
- name: Link Checker
uses: Kilo-Org/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
fetch-depth: 1
@@ -128,7 +128,7 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
fetch-depth: 1
+5 -1
View File
@@ -14,7 +14,11 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
# kilocode_change start
with:
persist-credentials: false
# kilocode_change end
- name: Setup Bun
uses: ./.github/actions/setup-bun
-19
View File
@@ -40,8 +40,6 @@ jobs:
SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
PACKAGES="kilo"
# TODO: move 'desktop' to PACKAGES when #11755 is fixed
OPTIONAL_PACKAGES="desktop"
echo ""
echo "=== Evaluating packages for all systems ==="
@@ -61,23 +59,6 @@ jobs:
done
done
echo ""
echo "=== Evaluating optional packages ==="
for system in $SYSTEMS; do
echo ""
echo "--- $system ---"
for pkg in $OPTIONAL_PACKAGES; do
printf " %s: " "$pkg"
if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
echo "✓"
else
echo "✗"
echo "::warning::Evaluation failed for packages.$system.$pkg"
echo "$output"
fi
done
done
echo ""
echo "=== Evaluating devShells for all systems ==="
for system in $SYSTEMS; do
+3 -3
View File
@@ -73,7 +73,7 @@ jobs:
echo "Computed hash for ${SYSTEM}: $HASH"
- name: Upload hash
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: hash-${{ matrix.system }}
path: hash.txt
@@ -86,7 +86,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
persist-credentials: false
fetch-depth: 0
@@ -106,7 +106,7 @@ jobs:
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
- name: Download hash artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8 # kilocode_change
with:
path: hashes
pattern: hash-*
+26 -136
View File
@@ -42,7 +42,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'Kilo-Org/kilocode'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6 # kilocode_change
with:
fetch-depth: 0
@@ -78,7 +78,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'Kilo-Org/kilocode'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6 # kilocode_change
with:
fetch-tags: true
@@ -99,19 +99,20 @@ jobs:
# download-artifact@v4's streaming unzip is CPU-bound and processes entries one by one;
# collapsing ~480 files into one tar cuts build-vscode download from >30m to seconds.
# zstd compresses the wasms (~4x) and leaves the already-compact binaries alone, for
# ~1 GB artifact. compression-level: 0 on the outer zip avoids double-compression.
# zstd + GNU tar are preinstalled on both github-hosted and blacksmith ubuntu-2404.
# See actions/upload-artifact#36, actions/toolkit#1533.
# ~1 GB artifact. upload-artifact@v7's `archive: false` skips the zip wrapper entirely
# so the tarball travels as-is (download-artifact@v8 auto-detects via Content-Type).
# With `archive: false` the `name:` input is ignored; the artifact is named after the
# file (kilo-cli.tar.zst). zstd + GNU tar are preinstalled on both github-hosted and
# blacksmith ubuntu-2404. See actions/upload-artifact#36, actions/toolkit#1533.
- name: Pack CLI dist into tar.zst
run: |
find packages/opencode/dist -name '*.map' -delete
tar --zstd -cf /tmp/kilo-cli.tar.zst -C packages/opencode/dist .
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: kilo-cli
path: /tmp/kilo-cli.tar.zst
compression-level: 0
archive: false
# kilocode_change end
outputs:
@@ -122,11 +123,11 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'Kilo-Org/kilocode'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6 # kilocode_change
- uses: ./.github/actions/setup-bun
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6 # kilocode_change
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
@@ -135,10 +136,11 @@ jobs:
run: bun install -g @vscode/vsce
# kilocode_change start - download into /tmp and extract the tar.zst packed by build-cli
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: kilo-cli
name: kilo-cli.tar.zst
path: /tmp
skip-decompress: true
- name: Unpack CLI dist
run: |
@@ -154,127 +156,11 @@ jobs:
KILO_PRE_RELEASE: ${{ inputs.pre_release }}
GH_REPO: ${{ github.repository }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7 # kilocode_change
with:
name: kilo-vscode
path: packages/kilo-vscode/out
# build-tauri:
# needs:
# - build-cli
# - version
# continue-on-error: false
# strategy:
# fail-fast: false
# matrix:
# settings:
# - host: macos-latest
# target: x86_64-apple-darwin
# - host: macos-latest
# target: aarch64-apple-darwin
# - host: blacksmith-4vcpu-windows-2025
# target: x86_64-pc-windows-msvc
# - host: blacksmith-4vcpu-ubuntu-2404
# target: x86_64-unknown-linux-gnu
# - host: blacksmith-8vcpu-ubuntu-2404-arm
# target: aarch64-unknown-linux-gnu
# runs-on: ${{ matrix.settings.host }}
# steps:
# - uses: actions/checkout@v3
# with:
# fetch-tags: true
# - uses: apple-actions/import-codesign-certs@v2
# if: ${{ runner.os == 'macOS' }}
# with:
# keychain: build
# p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
# p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# - name: Verify Certificate
# if: ${{ runner.os == 'macOS' }}
# run: |
# CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
# CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
# echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
# echo "Certificate imported."
# - name: Setup Apple API Key
# if: ${{ runner.os == 'macOS' }}
# run: |
# echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
# - uses: ./.github/actions/setup-bun
# - name: Cache apt packages
# if: contains(matrix.settings.host, 'ubuntu')
# uses: actions/cache@v4
# with:
# path: /var/cache/apt/archives
# key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }}
# restore-keys: |
# ${{ runner.os }}-${{ matrix.settings.target }}-apt-
# - name: install dependencies (ubuntu only)
# if: contains(matrix.settings.host, 'ubuntu')
# run: |
# sudo apt-get update
# sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# - name: install Rust stable
# uses: dtolnay/rust-toolchain@stable
# with:
# targets: ${{ matrix.settings.target }}
# - uses: Swatinem/rust-cache@v2
# with:
# workspaces: packages/desktop/src-tauri
# shared-key: ${{ matrix.settings.target }}
# - name: Prepare
# run: |
# cd packages/desktop
# bun ./scripts/prepare.ts
# env:
# KILO_VERSION: ${{ needs.version.outputs.version }}
# GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
# RUST_TARGET: ${{ matrix.settings.target }}
# GH_TOKEN: ${{ github.token }}
# GITHUB_RUN_ID: ${{ github.run_id }}
# # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
# - name: Install tauri-cli from portable appimage branch
# if: contains(matrix.settings.host, 'ubuntu')
# run: |
# cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force
# echo "Installed tauri-cli version:"
# cargo tauri --version
# - name: Build and upload artifacts
# uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
# timeout-minutes: 60
# with:
# projectPath: packages/desktop
# uploadWorkflowArtifacts: true
# tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
# args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
# updaterJsonPreferNsis: true
# releaseId: ${{ needs.version.outputs.release }}
# tagName: ${{ needs.version.outputs.tag }}
# releaseDraft: true
# releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
# TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
# APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
# APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
# APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
# APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
# kilocode_change start
# Run smoke tests against CLI assets uploaded to the draft GitHub release
# before publishing the release and package artifacts.
@@ -295,10 +181,13 @@ jobs:
- build-cli
- build-vscode
- smoke-test
# - build-tauri
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6 # kilocode_change
# kilocode_change start
with:
persist-credentials: false
# kilocode_change end
- uses: ./.github/actions/setup-bun
@@ -315,7 +204,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6 # kilocode_change
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
@@ -333,23 +222,24 @@ jobs:
# kilocode_change end
# kilocode_change start - download into /tmp and extract the tar.zst packed by build-cli
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: kilo-cli
name: kilo-cli.tar.zst
path: /tmp
skip-decompress: true
- name: Unpack CLI dist
run: |
mkdir -p packages/opencode/dist
tar --zstd -xf /tmp/kilo-cli.tar.zst -C packages/opencode/dist
# kilocode_change end
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: kilo-vscode
path: packages/kilo-vscode/out
- name: Cache apt packages (AUR)
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: /var/cache/apt/archives
key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
+2 -2
View File
@@ -47,7 +47,7 @@ jobs:
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
steps:
- name: Checkout kilo-bench
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
repository: Kilo-Org/kilo-bench
token: ${{ secrets.BENCH_GITHUB_TOKEN }}
@@ -122,7 +122,7 @@ jobs:
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: smoke-test-results
path: |
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
if: github.repository == 'Kilo-Org/kilocode'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
- uses: oven-sh/setup-bun@v2
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
token: ${{ secrets.GITHUB_TOKEN }}
+6 -87
View File
@@ -28,21 +28,21 @@ jobs:
matrix:
settings:
- name: linux
host: blacksmith-8vcpu-ubuntu-2404
host: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
- name: windows
host: blacksmith-8vcpu-windows-2025
host: blacksmith-4vcpu-windows-2025 # kilocode_change
runs-on: ${{ matrix.settings.host }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6 # kilocode_change
with:
node-version: "24"
@@ -55,7 +55,7 @@ jobs:
git config --global user.name "kilo-maintainer[bot]"
- name: Cache Turbo
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: node_modules/.cache/turbo
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }}
@@ -80,7 +80,7 @@ jobs:
- name: Upload unit artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }}
include-hidden-files: true
@@ -88,87 +88,6 @@ jobs:
retention-days: 7
path: packages/*/.artifacts/unit/junit.xml
e2e:
# kilocode_change - disabled: packages/app is not actively maintained
if: false
name: e2e (${{ matrix.settings.name }})
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404
- name: windows
host: blacksmith-4vcpu-windows-2025
runs-on: ${{ matrix.settings.host }}
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Read Playwright version
id: playwright-version
run: |
version=$(node -e 'console.log(require("./package.json").workspaces.catalog["@playwright/test"])')
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.playwright-browsers
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium
- name: Install Playwright system dependencies
if: runner.os == 'Linux'
working-directory: packages/app
run: bunx playwright install-deps chromium
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: packages/app
run: bunx playwright install chromium
- name: Run app e2e tests
run: bun --cwd packages/app test:e2e:local
env:
CI: true
# kilocode_change start
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
KILO_ORG_ID: ${{ secrets.KILO_ORG_ID }}
KILO_DISABLE_SHARE: "true"
KILO_DISABLE_SESSION_INGEST: "true"
KILO_E2E_REQUIRE_PAID: "true"
# kilocode_change end
PLAYWRIGHT_JUNIT_OUTPUT: e2e/junit-${{ matrix.settings.name }}.xml
timeout-minutes: 30
- name: Upload Playwright artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
if-no-files-found: ignore
retention-days: 7
path: |
packages/app/e2e/junit-*.xml
packages/app/e2e/test-results
packages/app/e2e/playwright-report
required:
name: test (linux)
runs-on: blacksmith-4vcpu-ubuntu-2404
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
fetch-depth: 1
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
- name: Setup Bun
uses: ./.github/actions/setup-bun
+13 -13
View File
@@ -12,7 +12,7 @@ jobs:
matched: ${{ steps.filter.outputs.matched }}
is_fork: ${{ steps.fork-check.outputs.is_fork }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6 # kilocode_change
- uses: Kilo-Org/paths-filter@master
id: filter
with:
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Checkout (internal)
if: needs.check-paths.outputs.is_fork != 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
lfs: true
token: ${{ secrets.BOT_PAT }}
@@ -55,7 +55,7 @@ jobs:
- name: Checkout (fork)
if: needs.check-paths.outputs.is_fork == 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
lfs: true
@@ -77,7 +77,7 @@ jobs:
bun-version: latest
- name: Cache Bun modules
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lock') }}
@@ -87,7 +87,7 @@ jobs:
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('packages/kilo-ui/package.json') }}
@@ -103,7 +103,7 @@ jobs:
- name: Cache Storybook build
id: storybook-cache
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: packages/kilo-ui/storybook-static
key: storybook-${{ hashFiles('packages/kilo-ui/src/**', 'packages/kilo-ui/.storybook/**', 'packages/ui/src/**', 'packages/kilo-ui/package.json') }}
@@ -195,7 +195,7 @@ jobs:
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: visual-regression-results
path: packages/kilo-ui/test-results/
@@ -211,7 +211,7 @@ jobs:
steps:
- name: Checkout (internal)
if: needs.check-paths.outputs.is_fork != 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
lfs: true
token: ${{ secrets.BOT_PAT }}
@@ -219,7 +219,7 @@ jobs:
- name: Checkout (fork)
if: needs.check-paths.outputs.is_fork == 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6 # kilocode_change
with:
lfs: true
@@ -241,7 +241,7 @@ jobs:
bun-version: latest
- name: Cache Bun modules
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lock') }}
@@ -251,7 +251,7 @@ jobs:
- name: Cache Playwright browsers
id: playwright-cache-vscode
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: ~/.cache/ms-playwright
key: playwright-vscode-${{ hashFiles('packages/kilo-vscode/package.json') }}
@@ -267,7 +267,7 @@ jobs:
- name: Cache Storybook build
id: storybook-cache-vscode
uses: actions/cache@v4
uses: actions/cache@v5 # kilocode_change
with:
path: packages/kilo-vscode/storybook-static
key: storybook-vscode-${{ hashFiles('packages/kilo-vscode/webview-ui/src/**', 'packages/kilo-vscode/.storybook/**', 'packages/kilo-ui/src/**', 'packages/ui/src/**', 'packages/kilo-vscode/package.json') }}
@@ -386,7 +386,7 @@ jobs:
- name: Upload test results on failure
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: visual-regression-vscode-results
path: packages/kilo-vscode/test-results/
+263
View File
@@ -0,0 +1,263 @@
---
description: Resolve upstream merge conflicts
---
Resolve the manual part of an upstream merge.
Arguments: `$ARGUMENTS`
Use the first argument as the upstream version, for example `v1.1.50` or
`1.1.50`. If no argument is provided, infer the version from the current branch
name, `upstream-merge-report-<version>.md`, or the newest relevant report file.
## Workflow
### 1. Inspect the current merge state
- `git status --short`
- `git diff --name-only --diff-filter=U`
- `upstream-merge-report-<version>.md` when present
- `.worktrees/opencode-merge/auto-merge` for the automated merge snapshot when present
### 2. Read every conflicted file end-to-end before planning
Use `script/upstream/find-conflict-markers.sh <file>` to jump to each region,
then read enough surrounding lines to understand the code — not just the
conflict hunk. Specifically check:
- is this a plain 3-way on a single expression, or a structural refactor?
- does upstream rename/move something that invalidates a HEAD-only declaration?
- does a `kilocode_change` marker in HEAD encode a bug fix, a feature, or a
defensive check?
- is the conflicted block referenced by *non-conflicted* code elsewhere in the
same file (imports, signatures, call sites) that will break if we drop it?
When HEAD includes a non-obvious Kilo-specific wrapper (e.g. a helper in
`packages/opencode/src/kilocode/`), find out why it exists before deciding to
keep or bypass it:
```bash
git log --all --oneline -S "<symbol>" -- packages/opencode/src/kilocode/
git log --all --oneline -- <kilo-file>
```
Look at the commit message and any PR reference. "We wrote our own because of
PR #NNNN" is a real constraint; "we wrote our own because of a typo" is not.
### 3. Write a plan in chat and get approval
For every conflicted file (and any adjacent file the resolution forces you to
touch — see §6) include:
- expected resolution kind: `hybrid`, `take-ours`, `take-theirs`, `regenerated`,
`removed`, `renamed`, or `other`
- risk level: `low`, `medium`, or `high`
- one-sentence rationale (what Kilo behaviour is preserved, what upstream
feature is adopted, what is dropped)
- verification commands you expect to run (targeted tests, typecheck)
Group files by risk level. Ask the user which batch to start with. You can
resolve an entire `low` batch in one pass if the user approves the batch, but
resolve `medium` and `high` files one at a time.
**Do not resolve a file until the user has approved that file's (or batch's)
strategy.**
### 4. Before every edit, explain reasoning before showing the diff
The user needs to review intent, not just the raw change. For each file, in
order:
1. Show the conflict's surrounding context (10–30 lines around each conflict
region, in chat).
2. Explain what each of the three sides (HEAD, merge-base, upstream) is doing.
3. State which Kilo behaviour must survive and why (reference PR numbers /
`kilocode_change` comments when possible).
4. State the resolution and why it is better than the alternatives.
5. Then apply the edit. The tool will display the diff — the user only has to
verify the diff matches the reasoning.
Do not lead with the diff. A diff without reasoning forces the user to
reverse-engineer the decision.
### 5. Apply resolution rules
Reference worktrees when present:
- `.worktrees/opencode-merge/opencode` — pristine upstream tree
- `.worktrees/opencode-merge/kilo-main` — Kilo base snapshot
- `.worktrees/opencode-merge/auto-merge` — automated merge snapshot (original
conflict reference)
Apply in order:
- prefer upstream code and architecture whenever compatible with Kilo behaviour
- preserve Kilo-specific behaviour marked with `kilocode_change`
- keep `kilocode_change` markers around Kilo-specific code in shared opencode
files
- when upstream refactors a region that HEAD had annotated with
`kilocode_change`, **check whether the marker encodes a bug fix or a feature
delta**. Bug fixes (missing `await`, defensive null-check, error capture)
usually need to be re-applied on top of the upstream refactor. Example from
v1.14.30: `Workspace.isSyncing` was missing an `await` — upstream's Effect
refactor reintroduced the same bug, so we had to port the fix into the new
`Effect.gen` block.
- when a `take-theirs` drops a line that was the target of a Kilo pre-filter,
the upstream line may be actively wrong for Kilo — e.g. an inner `continue`
filter whose condition collides with an outer filter Kilo added. Re-read the
surrounding 20 lines before committing to `take-theirs`.
- if Kilo-specific code must be refactored to fit new upstream architecture,
explain the refactor in the final summary
- if upstream moved the relevant logic to another file, port the Kilo behaviour
there and list both paths in the final summary. Verify the new file already
carries the Kilo-renamed symbols (e.g. `x-kilo-directory`) by diffing against
pristine upstream.
- if upstream deleted a file, analyse whether the Kilo behaviour should be
ported elsewhere or removed rather than restoring the deleted file
- if tests fail only because upstream intentionally removed behaviour, remove
or update the obsolete tests rather than adding the old file back
- do not modify unrelated files
When removing code that existed in one side of a conflict, prefer
**commenting it out with `kilocode_change` markers** over deletion when the
surrounding structure (an `if`, a loop) still makes sense. That keeps the
intent visible to the next merger. Example:
```ts
} else if (input?.scope !== "project" && !Flag.KILO_EXPERIMENTAL_WORKSPACES) {
// kilocode_change start - directory filtering handled by KiloSession.filters above
// if (input?.directory) {
// conditions.push(eq(SessionTable.directory, input.directory))
// }
// kilocode_change end
}
```
Use `TODO:` not `NOTE:` for follow-ups. `TODO` is searchable and implies an
owner will act on it; `NOTE` reads as permanent commentary.
### 6. Look for adjacent files the conflict forces you to touch
Upstream restructures sometimes split one file into several (e.g. `permission.ts`
→ `groups/permission.ts` + `handlers/permission.ts`). Only the *renamed* file
shows up in `git diff --diff-filter=U`; the new sibling may need a Kilo feature
ported in too. After resolving the flagged file, check:
- files that import from the resolved file — do they compile?
- files at paths implied by new imports (e.g. `../middleware/*`, `./handlers/*`)
- `kilocode_change` comments in the *auto-merge* snapshot that didn't end up in
the working tree because the hosting file was renamed
Add any such files to the plan as `hybrid` or `take-ours` with the same
approval flow.
### 7. Verify each resolution before moving on
- confirm `script/upstream/find-conflict-markers.sh <file>` prints nothing
- read the final file region (the new shape after edit) and sanity-check imports
- for apparently-unused symbols upstream introduced, `grep` the file and the
rest of the package before deleting — they may be called from non-conflicted
code elsewhere. Example: `isTheme` in `theme.tsx` looked unused at the
resolution site but was called twice further down.
- run the smallest relevant check (single `bun test` file, or `bun run
typecheck` in the touched package)
- summarise the exact resolution, tradeoff, and verification result in chat
- ask the user to approve the resolved file before staging it or resolving the
next one (for `medium` / `high`; `low` batches can be staged together)
### 8. Run the full checks once everything is resolved
- `git diff --name-only --diff-filter=U` returns empty
- `bun run typecheck` from `packages/opencode/` (targeted) and from repo root
(catches non-conflicted call-site breakage)
- relevant targeted tests. Tests that hang or time out in an unrelated part of
the graph may be pre-existing — note them, don't block the merge on them
- `bun run script/check-opencode-annotations.ts` if `packages/opencode/` shared
files changed. Note that this tool compares against the merge base via `HEAD`
and will be silent until the merge commit lands
- other CI guards that touched files imply (knip for `kilo-vscode/`,
`check-kilocode-change`, source-links, visual regression)
### 9. Commit with the standard message
Per `script/upstream/README.md`:
```bash
git commit -m "resolve merge conflicts"
```
The default `git merge` auto-message (`Merge branch '…' into …`) is also fine,
but `resolve merge conflicts` is the convention for these PRs.
### 10. Resync version strings in a separate commit
Upstream stamps its own version into shared files — notably
`packages/extensions/zed/extension.toml` (version field + 5 Kilo-Org download
URLs), and any `package.json` that upstream bumped in the same release window.
After the merge this leaves parts of the tree pointing at upstream's version
(e.g. `1.14.30`), whose release tag does not exist on Kilo's pipeline, so the
Zed download URLs silently 404.
Fix this in a dedicated commit *after* `resolve merge conflicts`:
```bash
bun run script/sync-versions.ts # uses root package.json version
# or, to target an explicit version:
bun run script/sync-versions.ts 7.2.41
git add -A
git commit -m "chore: resync versions after upstream merge"
```
The script rewrites every top-level `"version"` in `package.json` files
(excluding `node_modules`, hidden dirs, and `packages/kilo-jetbrains/` which
tracks its own cadence), plus the Zed extension toml. It is idempotent — rerun
it any time to rebase the version back onto Kilo main (useful during
long-running upstream merges where `main` releases in the meantime).
Keeping this in its own commit makes reviewers' job easier: the merge commit
only contains behavioural resolutions, and the version resync is a trivial
diff they can skim in one glance.
### 11. Write the PR body
Structure the description so reviewers can skim:
- **Non-trivial merge decisions**: a short section per file (or group of
related files) that required more than a mechanical `take-ours`/`take-theirs`.
Focus on *what Kilo behaviour survived* and *what upstream features were
adopted*. Link to Kilo PRs when a `kilocode_change` encodes a specific fix.
- **Notable auto-merged changes**: new columns, new helper files, renamed
middleware — anything reviewers should eyeball even though git didn't flag
it.
- **What to test**: explicit, scenario-level test steps for each non-trivial
change. Don't list tests; list *user-visible behaviour* so a tester who
doesn't read the diff can exercise it.
- **CI guards to watch**: typecheck, knip, annotation check, visual regression.
- **Follow-ups**: any `TODO:` you left in code, as a bullet list with links.
## User-approval checkpoints
Every manual merge decision requires explicit user approval **before applying**
and **again after verification**. Be especially cautious when a decision is
destructive, changes auth, billing, data deletion, public API compatibility,
config schema behaviour, migrations, provider routing, or security posture.
## Common pitfalls
- Auto-merged code can reference declarations that still live inside conflict
blocks.
- Related sibling files can need edits even when they are not listed as
unmerged — especially after upstream structural splits.
- `renamed` should be used only when behaviour moves to a different file.
- Function signatures can drift across conflict boundaries (args added, return
types widened). Grep for every call site before finalising.
- Full-repo typecheck is the catch-all for non-conflicted call-site breakage.
- Upstream can reintroduce bugs a Kilo `kilocode_change` had already fixed —
during big refactors check every `kilocode_change` the refactor touched.
- "Take-theirs" on an inner conditional is often wrong when Kilo added an outer
pre-filter whose whole point was to widen what makes it to the inner block.
- Apparently-unused upstream-added declarations may be called from
non-conflicted code elsewhere. Grep before deleting.
- Stricter DOM lib types (upstream TS upgrade) can surface latent casting
issues around `WebSocket.send`, `Headers`, etc. — prefer narrowing the Kilo
type over adding `any` casts.
+8 -8
View File
@@ -5,16 +5,16 @@
"packages": {
"": {
"dependencies": {
"@kilocode/plugin": "7.2.24"
"@kilocode/plugin": "7.2.31"
}
},
"node_modules/@kilocode/plugin": {
"version": "7.2.24",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.24.tgz",
"integrity": "sha512-b2Xh3N/KaGdYj9hZbzZLKlDHXUsdMey0zL8G+RsnxDQCuhNm46LPyixtlJQhbCyir/zEEvy3uJI9kqKCsOWmQg==",
"version": "7.2.31",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.31.tgz",
"integrity": "sha512-KmKTTIly7hRlJdXKhqZ/j/brvTPh0z0UTjWSjJWq5fqf4pATgYGn7G0g3ZjILnN7MUkkZXuljgqExTEeQJHGkQ==",
"license": "MIT",
"dependencies": {
"@kilocode/sdk": "7.2.24",
"@kilocode/sdk": "7.2.31",
"effect": "4.0.0-beta.48",
"zod": "4.1.8"
},
@@ -32,9 +32,9 @@
}
},
"node_modules/@kilocode/sdk": {
"version": "7.2.24",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.24.tgz",
"integrity": "sha512-Yos6WeyvMl86OGXLQqQ9YWDm/geAC77xetoptEsS3DLtPVgvz1Nqkvas77fAuAqwkSfGL2mLIiHUTqJUTVflOw==",
"version": "7.2.31",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.31.tgz",
"integrity": "sha512-Sx05yv+3TIlc6M4Ze+YGgKCLoIg8B0WRE15JXDriVncT+wz7M6+e+4mjNWkwfsuywdeOTYPfeFViqX8iO7ckKA==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
@@ -0,0 +1,119 @@
---
name: kilocode-merge-minimizer
description: Use when changing shared upstream-owned files, editing or reviewing `kilocode_change` markers, or moving additive Kilo-specific behavior into Kilo-owned code to reduce future merge conflicts.
---
# Kilo Merge Minimizer
Use this skill whenever a normal development task touches shared upstream-owned code and includes Kilo-specific behavior, especially for marker cleanup, extraction work, or `kilocode_change` annotations.
Do not use this as the primary guide for upstream merge resolution. Upstream merges have their own instructions and should not duplicate that workflow here.
## Goal
Minimize Kilo's long-term diff against upstream OpenCode while preserving behavior.
Prefer this shape for Kilo-specific additions:
1. Shared upstream file contains only a minimal hook, import, call, registration, or config entry.
2. Kilo-specific behavior lives in Kilo-owned code.
3. Unavoidable shared-file changes have narrow `kilocode_change` markers.
4. The annotation checker passes.
For changes to existing upstream behavior, prefer the smallest in-place shared-file diff with narrow markers. Do not move changed upstream logic into Kilo-owned code just to avoid textual conflicts, because that can create harder semantic merge conflicts.
## Core Rules
- Use `script/check-opencode-annotations.ts` as the source of truth for current shared scopes and exempt paths.
- Use `script/upstream/fix-kilocode-markers.ts` for stale or broad markers, inspecting `--dry-run` output before applying changes.
- Treat upstream-owned files as shared unless the checker or repo ownership rules exempt them.
- Put Kilo-owned UI, CLI, runtime logic, and tests in Kilo-owned paths where practical.
- Avoid adding Kilo business logic directly to shared files.
- Keep shared-file edits as close as possible to upstream shape.
- Do not change shared files unless the change is required for Kilo functionality, fixes a Kilo bug, or is a minimal targeted upstream-quality fix.
- Do not create a large Kilo-only fork for a general upstream-quality improvement. Prefer a minimal targeted fix, or leave the broader change for upstream.
- Do not duplicate upstream logic unless there is a concrete reason. If duplication is unavoidable, isolate the Kilo delta and keep the upstream dependency obvious.
## Shared File Structure
- Do not refactor, rename, split files, or extract helpers in shared files just to improve readability or make Kilo extraction cleaner.
- Avoid structural changes that make upstream behavior harder to compare or hide semantic dependency on upstream code.
## Shared File Style
- Preserve upstream formatting and import style in shared files, even when it differs from Kilo style.
- Put Kilo-only imports on separate marked lines instead of reorganizing upstream imports.
## Decision Rules
Extract Kilo logic when:
- The change is an additive Kilo feature or integration, not a modification of existing upstream behavior.
- The shared-file change has meaningful Kilo-owned behavior, not just a tiny condition, import, registration, or field.
- The code has loops, branching, error handling, async workflows, storage access, network calls, UI rendering, or telemetry.
- The shared file can become a small orchestrator that calls Kilo helpers.
- The Kilo code is independent enough that extraction will not hide future upstream fixes or behavior changes.
Keep the change inline when:
- The Kilo delta is a single field, import, call, simple condition, or small registry entry.
- Extraction would reshape upstream code more than the Kilo change itself.
- The change modifies an upstream algorithm, ordering, heuristic, control flow, or bug fix.
- Extraction would duplicate upstream logic or hide semantic dependency on upstream behavior.
- The Kilo helper closes over upstream-local state. Keep closure-scoped helpers inline and contiguous in one narrow marker block.
- The shared file owns the only route table, enum, schema, switch, or registry where the hook must exist.
- The change restores upstream shape or removes a stale Kilo divergence.
Always preserve upstream behavior order unless the Kilo behavior change is intentional and tested.
## Marker Rules
- Mark only Kilo-specific diff lines in shared upstream files.
- Prefer inline markers for single-line changes: `const value = 42 // kilocode_change`.
- Use block markers only for adjacent Kilo-specific lines:
```ts
// kilocode_change start
registerKiloFeature(app)
// kilocode_change end
```
- Use the file's native comment style, including JSX block comments inside JSX and `#` comments for YAML, TOML, and shell.
- Do not add markers in checker-exempt Kilo-owned paths.
- Remove stale markers when upstream already contains the behavior or when touching Kilo-owned files that still have old markers.
- Use `// kilocode_change - new file` only for unavoidable new Kilo-specific files inside shared upstream paths.
## Tests
- Put Kilo-specific CLI/runtime tests in Kilo-owned test paths.
- Move tests out of shared upstream test paths when the behavior under test is Kilo-specific.
- Tests should cover the real failing path, not private or unstable APIs chosen only for convenience.
- Do not add skip gates for required regression coverage.
## Verification
After editing shared files or marker comments, run:
```bash
bun run script/check-opencode-annotations.ts
```
If the PR uses a non-default comparison base, pass the correct base ref:
```bash
bun run script/check-opencode-annotations.ts --base <base-ref>
```
For stale or broad markers in one shared file, inspect the dry run before applying:
```bash
bun run script/upstream/fix-kilocode-markers.ts <repo-relative-file> --dry-run
```
Before finishing, confirm:
- Shared files contain minimal integration points only.
- Kilo logic and tests live in Kilo-owned paths where practical.
- Markers are narrow.
- Stale markers are removed.
- The annotation checker passed, or the reason it could not run is reported.
+8 -8
View File
@@ -5,16 +5,16 @@
"packages": {
"": {
"dependencies": {
"@kilocode/plugin": "7.2.24"
"@kilocode/plugin": "7.2.31"
}
},
"node_modules/@kilocode/plugin": {
"version": "7.2.24",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.24.tgz",
"integrity": "sha512-b2Xh3N/KaGdYj9hZbzZLKlDHXUsdMey0zL8G+RsnxDQCuhNm46LPyixtlJQhbCyir/zEEvy3uJI9kqKCsOWmQg==",
"version": "7.2.31",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.31.tgz",
"integrity": "sha512-KmKTTIly7hRlJdXKhqZ/j/brvTPh0z0UTjWSjJWq5fqf4pATgYGn7G0g3ZjILnN7MUkkZXuljgqExTEeQJHGkQ==",
"license": "MIT",
"dependencies": {
"@kilocode/sdk": "7.2.24",
"@kilocode/sdk": "7.2.31",
"effect": "4.0.0-beta.48",
"zod": "4.1.8"
},
@@ -32,9 +32,9 @@
}
},
"node_modules/@kilocode/sdk": {
"version": "7.2.24",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.24.tgz",
"integrity": "sha512-Yos6WeyvMl86OGXLQqQ9YWDm/geAC77xetoptEsS3DLtPVgvz1Nqkvas77fAuAqwkSfGL2mLIiHUTqJUTVflOw==",
"version": "7.2.31",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.31.tgz",
"integrity": "sha512-Sx05yv+3TIlc6M4Ze+YGgKCLoIg8B0WRE15JXDriVncT+wz7M6+e+4mjNWkwfsuywdeOTYPfeFViqX8iO7ckKA==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
+19 -10
View File
@@ -1,21 +1,30 @@
---
name: effect
description: Answer questions about the Effect framework
description: Work with Effect v4 / effect-smol TypeScript code in this repo
---
# Effect
This codebase uses Effect, a framework for writing typescript.
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
## How to Answer Effect Questions
## Source Of Truth
1. Clone the Effect repository: `https://github.com/Effect-TS/effect-smol` to
`.opencode/references/effect-smol` in this project NOT the skill folder.
2. Use the explore agent to search the codebase for answers about Effect patterns, APIs, and concepts
3. Provide responses based on the actual Effect source code and documentation
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
## Guidelines
- Always use the explore agent with the cloned repository when answering Effect-related questions
- Reference specific files and patterns found in the Effect codebase
- Do not answer from memory - always verify against the source
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows.
- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces.
- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services.
- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so.
- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see.
- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior.
- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types.
- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first.
+2 -2
View File
@@ -3,8 +3,8 @@ import { tool } from "@kilocode/plugin"
const TEAM = {
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
zen: ["fwang", "MrMushrooooom"],
tui: ["thdxr", "kommander", "rekram1-node"],
core: ["thdxr", "rekram1-node", "jlongster"],
tui: ["kommander", "rekram1-node", "simonklee"],
core: ["kitlangton", "rekram1-node", "jlongster"],
docs: ["R44VC0RP"],
windows: ["Hona"],
} as const
+12 -6
View File
@@ -21,6 +21,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang
- **Source links**: After adding or changing URLs in `packages/kilo-vscode/`, `packages/kilo-vscode/webview-ui/`, or `packages/opencode/src/`, run `bun run script/extract-source-links.ts` from the repo root and commit the updated `packages/kilo-docs/source-links.md`. CI runs this check — the build fails if the file is stale.
- **kilocode_change check**: `bun run check-kilocode-change` from `packages/kilo-vscode/`. CI runs this — `kilocode_change` is a marker for upstream merge conflicts and must not appear in `packages/kilo-vscode/` or `packages/kilo-ui/` (these are entirely Kilo Code additions). Remove the markers before pushing.
- **opencode annotation check**: `bun run script/check-opencode-annotations.ts` from repo root. CI runs this on PRs touching `packages/opencode/` — every Kilo-specific change in shared opencode files must be annotated with `kilocode_change` markers. Exempt paths (no markers needed): `packages/opencode/src/kilocode/`, `packages/opencode/test/kilocode/`, and any path containing `kilocode` in the name.
- **workflow allowlist**: `bun run script/check-workflows.ts` from repo root. CI runs this as part of the annotations workflow — any `.yml` / `.yaml` file added to or removed from `.github/workflows/` must be reflected in the hardcoded list in `script/check-workflows.ts`. Prevents upstream-merged workflows from silently starting to run in our CI.
- **Backend/SDK programmatic testing**: see [TESTING.md](./TESTING.md) for spawning the local main-branch backend (`bun dev serve`) and driving it via `curl` — use this instead of `kilo serve` (prod binary) when testing backend fixes.
## Quality Checks
@@ -43,15 +44,18 @@ All products are clients of the **CLI** (`packages/opencode/`), which contains t
| Product | Package | Description |
|---|---|---|
| Kilo CLI | `packages/opencode/` | Core engine. TUI, `kilo run`, `kilo serve`, `kilo web`. Fork of upstream OpenCode. |
| Kilo CLI | `packages/opencode/` | Core engine. TUI, `kilo run`, `kilo serve`. Fork of upstream OpenCode. |
| Kilo VS Code Extension | `packages/kilo-vscode/` | VS Code extension. Bundles the CLI binary, spawns `kilo serve` as a child process. Includes the **Agent Manager** — a multi-session orchestration panel with git worktree isolation. |
| OpenCode Desktop | `packages/desktop/` | Standalone Tauri native app. Bundles CLI as sidecar. Single-session UI. Unrelated to the VS Code extension. Not actively maintained — synced from upstream fork. |
| OpenCode Web | `packages/app/` | Shared SolidJS frontend used by both the desktop app and `kilo web` CLI command. Not actively maintained — synced from upstream fork. |
**Agent Manager** refers to a feature inside `packages/kilo-vscode/` (extension code in `src/agent-manager/`, webview in `webview-ui/agent-manager/`). It is not a standalone product. See the extension's `AGENTS.md` for details.
Extension-specific settings should live in the Kilo extension settings, not default VS Code settings, unless they are intentionally VS Code-wide.
## Package Instructions
- When a task primarily touches `packages/kilo-jetbrains/`, read `packages/kilo-jetbrains/AGENTS.md` before planning or editing.
- For JetBrains Kotlin/Swing UI work, also apply `packages/kilo-jetbrains/.kilo/skills/jetbrains-ui-style/SKILL.md`.
## Monorepo Structure
Turborepo + Bun workspaces. The packages you'll work with most:
@@ -64,9 +68,7 @@ Turborepo + Bun workspaces. The packages you'll work with most:
| `packages/kilo-gateway/` | `@kilocode/kilo-gateway` | Kilo auth, provider routing, API integration |
| `packages/kilo-telemetry/` | `@kilocode/kilo-telemetry` | PostHog analytics + OpenTelemetry |
| `packages/kilo-i18n/` | `@kilocode/kilo-i18n` | Internationalization / translations |
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library shared by the extension webview and `packages/app/` |
| `packages/app/` | `@opencode-ai/app` | Shared SolidJS web UI for desktop app and `kilo web` |
| `packages/desktop/` | `@opencode-ai/desktop` | Tauri desktop app shell |
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library shared by the extension webview and docs screenshot stories |
| `packages/util/` | `@opencode-ai/util` | Shared utilities (error, path, retry, slug, etc.) |
| `packages/plugin/` | `@kilocode/plugin` | Plugin/tool interface definitions |
@@ -254,6 +256,10 @@ We regularly merge upstream changes from opencode. To minimize merge conflicts a
The goal is to keep our diff from upstream as small as possible, making regular merges straightforward and reducing the risk of conflicts.
### Git conflict style
`bun install` sets `merge.conflictStyle=zdiff3` repo-locally via `script/setup-git.ts` (wired into `postinstall`). Conflicts include the common ancestor between `|||||||` and `=======`, which is what `script/upstream/` and `mergiraf` rely on for structural resolution and what makes manual resolution on shared opencode files tractable. If you've overridden it in your user config, the repo-local setting takes precedence — don't override it back.
### Kilocode Change Markers
To minimize merge conflicts when syncing with upstream, mark Kilo Code-specific changes in shared code with `kilocode_change` comments.
+1 -1
View File
@@ -1,6 +1,6 @@
# TESTING.md
How to spin up the **local main-branch** Kilo backend and test it with `curl` / `fetch`. Aimed at a running Kilo CLI agent iterating on backend fixes without rebuilding the VS Code extension, TUI, or desktop app.
How to spin up the **local main-branch** Kilo backend and test it with `curl` / `fetch`. Aimed at a running Kilo CLI agent iterating on backend fixes without rebuilding the VS Code extension or TUI.
All examples use plain shell + `curl`. Writing TypeScript files is a last resort (see Section 8).
+85 -999
View File
File diff suppressed because it is too large Load Diff
+1 -8
View File
@@ -267,13 +267,9 @@
opencode = final.callPackage ./nix/opencode.nix {
inherit node_modules;
};
desktop = final.callPackage ./nix/desktop.nix {
inherit opencode;
};
in
{
inherit opencode;
opencode-desktop = desktop;
};
};
@@ -286,13 +282,10 @@
kilo = pkgs.callPackage ./nix/kilo.nix {
inherit node_modules;
};
desktop = pkgs.callPackage ./nix/desktop.nix {
inherit kilo;
};
in
{
default = kilo;
inherit kilo desktop;
inherit kilo;
# Updater derivation with fakeHash - build fails and reveals correct hash
node_modules_updater = node_modules.override {
hash = pkgs.lib.fakeHash;
+64 -1
View File
@@ -385,6 +385,57 @@ add_to_path() {
fi
}
# Persistently add $INSTALL_DIR to the Windows user PATH via the registry.
# Works from Git Bash (MINGW64), MSYS2, and Cygwin.
# Converts the MSYS/Cygwin path to native Windows form so that cmd and
# PowerShell also see the entry.
add_to_windows_path() {
local win_install_dir
if command -v cygpath >/dev/null 2>&1; then
win_install_dir=$(cygpath -w "$INSTALL_DIR")
else
# Fallback: naive conversion /c/Users/... -> C:\Users\...
win_install_dir=$(echo "$INSTALL_DIR" | sed -e 's|^/\([a-zA-Z]\)/|\1:\\|' -e 's|/|\\|g')
fi
# Escape single-quotes for PowerShell single-quoted strings ('' is the escape sequence)
local win_install_dir_ps="${win_install_dir//\'/\'\'}"
local ps_cmd
ps_cmd=$(cat <<EOFPS
\$kiloPath = '$win_install_dir_ps'
\$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
\$pathParts = if ([string]::IsNullOrEmpty(\$currentPath)) { @() } else { \$currentPath -split ';' }
if (\$pathParts -notcontains \$kiloPath) {
\$newPath = if ([string]::IsNullOrEmpty(\$currentPath)) { \$kiloPath } else { "\$currentPath;\$kiloPath" }
[Environment]::SetEnvironmentVariable('Path', \$newPath, 'User')
Write-Output 'added'
}
EOFPS
)
local ps_output
if command -v powershell.exe >/dev/null 2>&1; then
ps_output=$(powershell.exe -NoProfile -NonInteractive -Command "$ps_cmd" 2>/dev/null) || return 1
elif command -v pwsh >/dev/null 2>&1; then
ps_output=$(pwsh -NoProfile -NonInteractive -Command "$ps_cmd" 2>/dev/null) || return 1
else
return 1
fi
if [[ "$ps_output" == *"added"* ]]; then
print_message info "${MUTED}Successfully added ${NC}kilo ${MUTED}to Windows user PATH${NC}"
fi
# Also add to the current bash session so `kilo` works immediately
# without the user needing to open a new terminal.
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
export PATH="$INSTALL_DIR:$PATH"
fi
return 0
}
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
current_shell=$(basename "$SHELL")
@@ -411,6 +462,13 @@ case $current_shell in
esac
if [[ "$no_modify_path" != "true" ]]; then
# Detect if we are running on Windows (MINGW, MSYS, Cygwin) regardless
# of whether the binary was installed from download or --binary.
_running_on_windows=false
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*) _running_on_windows=true ;;
esac
config_file=""
for file in $config_files; do
if [[ -f $file ]]; then
@@ -419,7 +477,12 @@ if [[ "$no_modify_path" != "true" ]]; then
fi
done
if [[ -z $config_file ]]; then
if [[ "$_running_on_windows" == "true" ]] && add_to_windows_path; then
# Handled via the Windows user-level PATH in the registry.
# The change is visible in new cmd / PowerShell / Git Bash sessions.
# We also exported it into the current session above.
:
elif [[ -z $config_file ]]; then
print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
+30
View File
@@ -0,0 +1,30 @@
{
"folders": [
{
"path": ".",
},
],
"settings": {
"window.title": "[main] Fix QuestionDock clickable options",
"peacock.color": "#ff3d00",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#ff6433",
"activityBar.background": "#ff6433",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#00ff3d",
"activityBarBadge.foreground": "#15202b",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#ff6433",
"statusBar.background": "#ff3d00",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#ff6433",
"statusBarItem.remoteBackground": "#ff3d00",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#ff3d00",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#ff3d0099",
"titleBar.inactiveForeground": "#e7e7e799"
},
},
}
-100
View File
@@ -1,100 +0,0 @@
{
lib,
stdenv,
rustPlatform,
pkg-config,
cargo-tauri,
bun,
nodejs,
cargo,
rustc,
jq,
wrapGAppsHook4,
makeWrapper,
dbus,
glib,
gtk4,
libsoup_3,
librsvg,
libappindicator,
glib-networking,
openssl,
webkitgtk_4_1,
gst_all_1,
kilo,
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "kilo-desktop";
inherit (kilo)
version
src
node_modules
patches
;
cargoRoot = "packages/desktop/src-tauri";
cargoLock.lockFile = ../packages/desktop/src-tauri/Cargo.lock;
buildAndTestSubdir = finalAttrs.cargoRoot;
nativeBuildInputs = [
pkg-config
cargo-tauri.hook
bun
nodejs # for patchShebangs node_modules
cargo
rustc
jq
makeWrapper
] ++ lib.optionals stdenv.hostPlatform.isLinux [ wrapGAppsHook4 ];
buildInputs = lib.optionals stdenv.isLinux [
dbus
glib
gtk4
libsoup_3
librsvg
libappindicator
glib-networking
openssl
webkitgtk_4_1
gst_all_1.gstreamer
gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good
gst_all_1.gst-plugins-bad
];
strictDeps = true;
preBuild = ''
cp -a ${finalAttrs.node_modules}/{node_modules,packages} .
chmod -R u+w node_modules packages
patchShebangs node_modules
patchShebangs packages/desktop/node_modules
mkdir -p packages/desktop/src-tauri/sidecars
cp ${kilo}/bin/kilo packages/desktop/src-tauri/sidecars/kilo-cli-${stdenv.hostPlatform.rust.rustcTarget}
'';
# see publish-tauri job in .github/workflows/publish.yml
tauriBuildFlags = [
"--config"
"tauri.prod.conf.json"
"--no-sign" # no code signing or auto updates
];
# FIXME: workaround for concerns about case insensitive filesystems
# should be removed once binary is renamed or decided otherwise
# darwin output is a .app bundle so no conflict
postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
mv $out/bin/Kilo $out/bin/kilo-desktop
sed -i 's|^Exec=Kilo$|Exec=kilo-desktop|' $out/share/applications/Kilo.desktop
'';
meta = {
description = "Kilo Desktop App";
homepage = "https://kilo.ai";
license = lib.licenses.mit;
mainProgram = "kilo-desktop";
inherit (kilo.meta) platforms;
};
})
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-4hLG583KyBLxAOYvIiVMh+xLtVAdR3QgWFEqnGlZLZw=",
"aarch64-linux": "sha256-+60DxQ/GKAAoMFuAQRFoYM0HlZMzpLpLrTD7qmweUUA=",
"aarch64-darwin": "sha256-3WCWVqaxu+lCIc51N7wOttM/hoXbbnmtw7EP2eGstQg=",
"x86_64-darwin": "sha256-K8iG+OHkgC/lwud6hQ91sXF3c7UbdQQYRU/KK2P+B/4="
"x86_64-linux": "sha256-BkFDOCEvivFrxKOvMjAIhj3QZwmb9vE/KvCZv5puw6E=",
"aarch64-linux": "sha256-sTkfQNjqCNCxmHbpQ8woA6azoiyD37e8Xy7UwB6eNsA=",
"aarch64-darwin": "sha256-lKuagsTHqij5C764DM3d+BOe4JKs2KV8XiwSlJItGMg=",
"x86_64-darwin": "sha256-g5gZMHLq8feSZHVaCOLGU6Tj2Qw56Zogif3pT9EIlz8="
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
[
ripgrep
]
# bun runs sysctl to detect if dunning on rosetta2
# bun runs sysctl to detect if running on rosetta2
++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
)
}
+1 -4
View File
@@ -30,7 +30,7 @@ stdenvNoCC.mkDerivation {
../bun.lock
../package.json
../patches
../install # required by desktop build (cli.rs include_str!)
../install
]
);
};
@@ -52,9 +52,6 @@ stdenvNoCC.mkDerivation {
--os="${bunOs}" \
--filter '!./' \
--filter './packages/opencode' \
--filter './packages/desktop' \
--filter './packages/app' \
--filter './packages/shared' \
--frozen-lockfile \
--ignore-scripts \
--no-progress
+8 -10
View File
@@ -8,13 +8,10 @@
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev-setup": "bun run --cwd packages/opencode --conditions=browser src/index.ts dev-setup",
"dev:desktop": "bun --cwd packages/desktop-electron dev",
"dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"typecheck": "bun turbo typecheck",
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
"postinstall": "bun run --cwd packages/opencode fix-node-pty && bun run script/setup-git.ts",
"prepare": "husky",
"random": "echo 'Random script'",
"hello": "echo 'Hello World!'",
@@ -27,15 +24,15 @@
"packages/sdk/js"
],
"catalog": {
"@effect/opentelemetry": "4.0.0-beta.48",
"@effect/platform-node": "4.0.0-beta.48",
"@effect/opentelemetry": "4.0.0-beta.57",
"@effect/platform-node": "4.0.0-beta.57",
"@npmcli/arborist": "9.4.0",
"@types/bun": "1.3.12",
"@types/cross-spawn": "6.0.6",
"@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2",
"@opentui/core": "0.1.99",
"@opentui/solid": "0.1.99",
"@opentui/core": "0.1.105",
"@opentui/solid": "0.1.105",
"ulid": "3.0.1",
"@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1",
@@ -46,13 +43,14 @@
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.1.0-beta.18",
"opentui-spinner": "0.0.6",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.4",
"dompurify": "3.3.3",
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
"effect": "4.0.0-beta.48",
"effect": "4.0.0-beta.57",
"ai": "6.0.168",
"cross-spawn": "7.0.6",
"hono": "4.12.12",
@@ -148,6 +146,6 @@
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"stream-chat@9.38.0": "patches/stream-chat@9.38.0.patch"
},
"version": "7.2.31",
"version": "7.2.42",
"peerDependencies": {}
}
-3
View File
@@ -1,3 +0,0 @@
src/assets/theme.css
e2e/test-results
e2e/playwright-report
-30
View File
@@ -1,30 +0,0 @@
## Debugging
- NEVER try to restart the app, or the server process, EVER.
## Local Dev
- `opencode dev web` proxies `https://app.opencode.ai`, so local UI/CSS changes will not show there.
- For local UI changes, run the backend and app dev servers separately.
- Backend (from `packages/opencode`): `bun run --conditions=browser ./src/index.ts serve --port 4096`
- App (from `packages/app`): `bun dev -- --port 4444`
- Open `http://localhost:4444` to verify UI changes (it targets the backend at `http://localhost:4096`).
## SolidJS
- Always prefer `createStore` over multiple `createSignal` calls
## Tool Calling
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
## Browser Automation
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
Core workflow:
1. `agent-browser open <url>` - Navigate to page
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
4. Re-snapshot after page changes
-50
View File
@@ -1,50 +0,0 @@
## Usage
Dependencies for these templates are managed with [pnpm](https://pnpm.io) using `pnpm up -Lri`.
This is the reason you see a `pnpm-lock.yaml`. That said, any package manager will work. This file can safely be removed once you clone a template.
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev` or `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## E2E Testing
Playwright starts the Vite dev server automatically via `webServer`, and UI tests expect an opencode backend at `localhost:4096` by default.
```bash
bunx playwright install chromium
bun run test:e2e:local
bun run test:e2e:local -- --grep "settings"
```
Environment options:
- `PLAYWRIGHT_SERVER_HOST` / `PLAYWRIGHT_SERVER_PORT` (backend address, default: `localhost:4096`)
- `PLAYWRIGHT_PORT` (Vite dev server port, default: `3000`)
- `PLAYWRIGHT_BASE_URL` (override base URL, default: `http://localhost:<PLAYWRIGHT_PORT>`)
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
-3
View File
@@ -1,3 +0,0 @@
[test]
root = "./src"
preload = ["./happydom.ts"]
@@ -1,515 +0,0 @@
# CreateEffect Simplification Implementation Spec
Reduce reactive misuse across `packages/app`.
---
## Context
This work targets `packages/app/src`, which currently has 101 `createEffect` calls across 37 files.
The biggest clusters are `pages/session.tsx` (19), `pages/layout.tsx` (13), `pages/session/file-tabs.tsx` (6), and several context providers that mirror one store into another.
Key issues from the audit:
- Derived state is being written through effects instead of computed directly
- Session and file resets are handled by watch-and-clear effects instead of keyed state boundaries
- User-driven actions are hidden inside reactive effects
- Context layers mirror and hydrate child stores with multiple sync effects
- Several areas repeat the same imperative trigger pattern in multiple effects
Keep the implementation focused on removing unnecessary effects, not on broad UI redesign.
## Goals
- Cut high-churn `createEffect` usage in the hottest files first
- Replace effect-driven derived state with reactive derivation
- Replace reset-on-key effects with keyed ownership boundaries
- Move event-driven work to direct actions and write paths
- Remove mirrored store hydration where a single source of truth can exist
- Leave necessary external sync effects in place, but make them narrower and clearer
## Non-Goals
- Do not rewrite unrelated component structure just to reduce the count
- Do not change product behavior, navigation flow, or persisted data shape unless required for a cleaner write boundary
- Do not remove effects that bridge to DOM, editors, polling, or external APIs unless there is a clearly safer equivalent
- Do not attempt a repo-wide cleanup outside `packages/app`
## Effect Taxonomy And Replacement Rules
Use these rules during implementation.
### Prefer `createMemo`
Use `createMemo` when the target value is pure derived state from other signals or stores.
Do this when an effect only reads reactive inputs and writes another reactive value that could be computed instead.
Apply this to:
- `packages/app/src/pages/session.tsx:141`
- `packages/app/src/pages/layout.tsx:557`
- `packages/app/src/components/terminal.tsx:261`
- `packages/app/src/components/session/session-header.tsx:309`
Rules:
- If no external system is touched, do not use `createEffect`
- Derive once, then read the memo where needed
- If normalization is required, prefer normalizing at the write boundary before falling back to a memo
### Prefer Keyed Remounts
Use keyed remounts when local UI state should reset because an identity changed.
Do this with `sessionKey`, `scope()`, or another stable identity instead of watching the key and manually clearing signals.
Apply this to:
- `packages/app/src/pages/session.tsx:325`
- `packages/app/src/pages/session.tsx:336`
- `packages/app/src/pages/session.tsx:477`
- `packages/app/src/pages/session.tsx:869`
- `packages/app/src/pages/session.tsx:963`
- `packages/app/src/pages/session/message-timeline.tsx:149`
- `packages/app/src/context/file.tsx:100`
Rules:
- If the desired behavior is "new identity, fresh local state," key the owner subtree
- Keep state local to the keyed boundary so teardown and recreation handle the reset naturally
### Prefer Event Handlers And Actions
Use direct handlers, store actions, and async command functions when work happens because a user clicked, selected, reloaded, or navigated.
Do this when an effect is just watching for a flag change, command token, or event-bus signal to trigger imperative logic.
Apply this to:
- `packages/app/src/pages/layout.tsx:484`
- `packages/app/src/pages/layout.tsx:652`
- `packages/app/src/pages/layout.tsx:776`
- `packages/app/src/pages/layout.tsx:1489`
- `packages/app/src/pages/layout.tsx:1519`
- `packages/app/src/components/file-tree.tsx:328`
- `packages/app/src/pages/session/terminal-panel.tsx:55`
- `packages/app/src/context/global-sync.tsx:148`
- Duplicated trigger sets in:
- `packages/app/src/pages/session/review-tab.tsx:122`
- `packages/app/src/pages/session/review-tab.tsx:130`
- `packages/app/src/pages/session/review-tab.tsx:138`
- `packages/app/src/pages/session/file-tabs.tsx:367`
- `packages/app/src/pages/session/file-tabs.tsx:378`
- `packages/app/src/pages/session/file-tabs.tsx:389`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
Rules:
- If the trigger is user intent, call the action at the source of that intent
- If the same imperative work is triggered from multiple places, extract one function and call it directly
### Prefer `onMount` And `onCleanup`
Use `onMount` and `onCleanup` for lifecycle-only setup and teardown.
This is the right fit for subscriptions, one-time wiring, timers, and imperative integration that should not rerun for ordinary reactive changes.
Use this when:
- Setup should happen once per owner lifecycle
- Cleanup should always pair with teardown
- The work is not conceptually derived state
### Keep `createEffect` When It Is A Real Bridge
Keep `createEffect` when it synchronizes reactive data to an external imperative sink.
Examples that should remain, though they may be narrowed or split:
- DOM/editor sync in `packages/app/src/components/prompt-input.tsx:690`
- Scroll sync in `packages/app/src/pages/session.tsx:685`
- Scroll/hash sync in `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
- External sync in:
- `packages/app/src/context/language.tsx:207`
- `packages/app/src/context/settings.tsx:110`
- `packages/app/src/context/sdk.tsx:26`
- Polling in:
- `packages/app/src/components/status-popover.tsx:59`
- `packages/app/src/components/dialog-select-server.tsx:273`
Rules:
- Keep the effect single-purpose
- Make dependencies explicit and narrow
- Avoid writing back into the same reactive graph unless absolutely required
## Implementation Plan
### Phase 0: Classification Pass
Before changing code, tag each targeted effect as one of: derive, reset, event, lifecycle, or external bridge.
Acceptance criteria:
- Every targeted effect in this spec is tagged with a replacement strategy before refactoring starts
- Shared helpers to be introduced are identified up front to avoid repeating patterns
### Phase 1: Derived-State Cleanup
Tackle highest-value, lowest-risk derived-state cleanup first.
Priority items:
- Normalize tabs at write boundaries and remove `packages/app/src/pages/session.tsx:141`
- Stop syncing `workspaceOrder` in `packages/app/src/pages/layout.tsx:557`
- Make prompt slash filtering reactive so `packages/app/src/components/prompt-input.tsx:652` can be removed
- Replace other obvious derived-state effects in terminal and session header
Acceptance criteria:
- No behavior change in tab ordering, prompt filtering, terminal display, or header state
- Targeted derived-state effects are deleted, not just moved
### Phase 2: Keyed Reset Cleanup
Replace reset-on-key effects with keyed ownership boundaries.
Priority items:
- Key session-scoped UI and state by `sessionKey`
- Key file-scoped state by `scope()`
- Remove manual clear-and-reseed effects in session and file context
Acceptance criteria:
- Switching session or file scope recreates the intended local state cleanly
- No stale state leaks across session or scope changes
- Target reset effects are deleted
### Phase 3: Event-Driven Work Extraction
Move event-driven work out of reactive effects.
Priority items:
- Replace `globalStore.reload` effect dispatching with direct calls
- Split mixed-responsibility effect in `packages/app/src/pages/layout.tsx:1489`
- Collapse duplicated imperative trigger triplets into single functions
- Move file-tree and terminal-panel imperative work to explicit handlers
Acceptance criteria:
- User-triggered behavior still fires exactly once per intended action
- No effect remains whose only job is to notice a command-like state and trigger an imperative function
### Phase 4: Context Ownership Cleanup
Remove mirrored child-store hydration patterns.
Priority items:
- Remove child-store hydration mirrors in `packages/app/src/context/global-sync/child-store.ts:184`, `:190`, `:193`
- Simplify mirror logic in `packages/app/src/context/global-sync.tsx:130`, `:138`
- Revisit `packages/app/src/context/layout.tsx:424` if it still mirrors instead of deriving
Acceptance criteria:
- There is one clear source of truth for each synced value
- Child stores no longer need effect-based hydration to stay consistent
- Initialization and updates both work without manual mirror effects
### Phase 5: Cleanup And Keeper Review
Clean up remaining targeted hotspots and narrow the effects that should stay.
Acceptance criteria:
- Remaining `createEffect` calls in touched files are all true bridges or clearly justified lifecycle sync
- Mixed-responsibility effects are split into smaller units where still needed
## Detailed Work Items By Area
### 1. Normalize Tab State
Files:
- `packages/app/src/pages/session.tsx:141`
Work:
- Move tab normalization into the functions that create, load, or update tab state
- Make readers consume already-normalized tab data
- Remove the effect that rewrites derived tab state after the fact
Rationale:
- Tabs should become valid when written, not be repaired later
- This removes a feedback loop and makes state easier to trust
Acceptance criteria:
- The effect at `packages/app/src/pages/session.tsx:141` is removed
- Newly created and restored tabs are normalized before they enter local state
- Tab rendering still matches current behavior for valid and edge-case inputs
### 2. Key Session-Owned State
Files:
- `packages/app/src/pages/session.tsx:325`
- `packages/app/src/pages/session.tsx:336`
- `packages/app/src/pages/session.tsx:477`
- `packages/app/src/pages/session.tsx:869`
- `packages/app/src/pages/session.tsx:963`
- `packages/app/src/pages/session/message-timeline.tsx:149`
Work:
- Identify state that should reset when `sessionKey` changes
- Move that state under a keyed subtree or keyed owner boundary
- Remove effects that watch `sessionKey` just to clear local state, refs, or temporary UI flags
Rationale:
- Session identity already defines the lifetime of this UI state
- Keyed ownership makes reset behavior automatic and easier to reason about
Acceptance criteria:
- The targeted reset effects are removed
- Changing sessions resets only the intended session-local state
- Scroll and editor state that should persist are not accidentally reset
### 3. Derive Workspace Order
Files:
- `packages/app/src/pages/layout.tsx:557`
Work:
- Stop writing `workspaceOrder` from live workspace data in an effect
- Represent user overrides separately from live workspace data
- Compute effective order from current data plus overrides with a memo or pure helper
Rationale:
- Persisted user intent and live source data should not mirror each other through an effect
- A computed effective order avoids drift and racey resync behavior
Acceptance criteria:
- The effect at `packages/app/src/pages/layout.tsx:557` is removed
- Workspace order updates correctly when workspaces appear, disappear, or are reordered by the user
- User overrides persist without requiring a sync-back effect
### 4. Remove Child-Store Mirrors
Files:
- `packages/app/src/context/global-sync.tsx:130`
- `packages/app/src/context/global-sync.tsx:138`
- `packages/app/src/context/global-sync.tsx:148`
- `packages/app/src/context/global-sync/child-store.ts:184`
- `packages/app/src/context/global-sync/child-store.ts:190`
- `packages/app/src/context/global-sync/child-store.ts:193`
- `packages/app/src/context/layout.tsx:424`
Work:
- Trace the actual ownership of global and child store values
- Replace hydration and mirror effects with explicit initialization and direct updates
- Remove the `globalStore.reload` event-bus pattern and call the needed reload paths directly
Rationale:
- Mirrors make it hard to tell which state is authoritative
- Event-bus style state toggles hide control flow and create accidental reruns
Acceptance criteria:
- Child store hydration no longer depends on effect-based copying
- Reload work can be followed from the event source to the handler without a reactive relay
- State remains correct on first load, child creation, and subsequent updates
### 5. Key File-Scoped State
Files:
- `packages/app/src/context/file.tsx:100`
Work:
- Move file-scoped local state under a boundary keyed by `scope()`
- Remove any effect that watches `scope()` only to reset file-local state
Rationale:
- File scope changes are identity changes
- Keyed ownership gives a cleaner reset than manual clear logic
Acceptance criteria:
- The effect at `packages/app/src/context/file.tsx:100` is removed
- Switching scopes resets only scope-local state
- No previous-scope data appears after a scope change
### 6. Split Layout Side Effects
Files:
- `packages/app/src/pages/layout.tsx:1489`
- Related event-driven effects near `packages/app/src/pages/layout.tsx:484`, `:652`, `:776`, `:1519`
Work:
- Break the mixed-responsibility effect at `:1489` into direct actions and smaller bridge effects only where required
- Move user-triggered branches into the actual command or handler that causes them
- Remove any branch that only exists because one effect is handling unrelated concerns
Rationale:
- Mixed effects hide cause and make reruns hard to predict
- Smaller units reduce accidental coupling and make future cleanup safer
Acceptance criteria:
- The effect at `packages/app/src/pages/layout.tsx:1489` no longer mixes unrelated responsibilities
- Event-driven branches execute from direct handlers
- Remaining effects in this area each have one clear external sync purpose
### 7. Remove Duplicate Triggers
Files:
- `packages/app/src/pages/session/review-tab.tsx:122`
- `packages/app/src/pages/session/review-tab.tsx:130`
- `packages/app/src/pages/session/review-tab.tsx:138`
- `packages/app/src/pages/session/file-tabs.tsx:367`
- `packages/app/src/pages/session/file-tabs.tsx:378`
- `packages/app/src/pages/session/file-tabs.tsx:389`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
- `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
Work:
- Extract one explicit imperative function per behavior
- Call that function from each source event instead of replicating the same effect pattern multiple times
- Preserve the scroll-sync effect that is truly syncing with the DOM, but remove duplicate trigger scaffolding around it
Rationale:
- Duplicate triggers make it easy to miss a case or fire twice
- One named action is easier to test and reason about
Acceptance criteria:
- Repeated imperative effect triplets are collapsed into shared functions
- Scroll behavior still works, including hash-based navigation
- No duplicate firing is introduced
### 8. Make Prompt Filtering Reactive
Files:
- `packages/app/src/components/prompt-input.tsx:652`
- Keep `packages/app/src/components/prompt-input.tsx:690` as needed
Work:
- Convert slash filtering into a pure reactive derivation from the current input and candidate command list
- Keep only the editor or DOM bridge effect if it is still needed for imperative syncing
Rationale:
- Filtering is classic derived state
- It should not need an effect if it can be computed from current inputs
Acceptance criteria:
- The effect at `packages/app/src/components/prompt-input.tsx:652` is removed
- Filtered slash-command results update correctly as the input changes
- The editor sync effect at `:690` still behaves correctly
### 9. Clean Up Smaller Derived-State Cases
Files:
- `packages/app/src/components/terminal.tsx:261`
- `packages/app/src/components/session/session-header.tsx:309`
Work:
- Replace effect-written local state with memos or inline derivation
- Remove intermediate setters when the value can be computed directly
Rationale:
- These are low-risk wins that reinforce the same pattern
- They also help keep follow-up cleanup consistent
Acceptance criteria:
- Targeted effects are removed
- UI output remains unchanged under the same inputs
## Verification And Regression Checks
Run focused checks after each phase, not only at the end.
### Suggested Verification
- Switch between sessions rapidly and confirm local session UI resets only where intended
- Open, close, and reorder tabs and confirm order and normalization remain stable
- Change workspaces, reload workspace data, and verify effective ordering is correct
- Change file scope and confirm stale file state does not bleed across scopes
- Trigger layout actions that previously depended on effects and confirm they still fire once
- Use slash commands in the prompt and verify filtering updates as you type
- Test review tab, file tab, and hash-scroll flows for duplicate or missing triggers
- Verify global sync initialization, reload, and child-store creation paths
### Regression Checks
- No accidental infinite reruns
- No double-firing network or command actions
- No lost cleanup for listeners, timers, or scroll handlers
- No preserved stale state after identity changes
- No removed effect that was actually bridging to DOM or an external API
If available, add or update tests around pure helpers introduced during this cleanup.
Favor tests for derived ordering, normalization, and action extraction, since those are easiest to lock down.
## Definition Of Done
This work is done when all of the following are true:
- The highest-leverage targets in this spec are implemented
- Each removed effect has been replaced by a clearer pattern: memo, keyed boundary, direct action, or lifecycle hook
- The "should remain" effects still exist only where they serve a real external sync purpose
- Touched files have fewer mixed-responsibility effects and clearer ownership of state
- Manual verification covers session switching, file scope changes, workspace ordering, prompt filtering, and reload flows
- No behavior regressions are found in the targeted areas
A reduced raw `createEffect` count is helpful, but it is not the main success metric.
The main success metric is clearer ownership and fewer effect-driven state repairs.
## Risks And Rollout Notes
Main risks:
- Keyed remounts can reset too much if state boundaries are drawn too high
- Store mirror removal can break initialization order if ownership is not mapped first
- Moving event work out of effects can accidentally skip triggers that were previously implicit
Rollout notes:
- Land in small phases, with each phase keeping the app behaviorally stable
- Prefer isolated PRs by phase or by file cluster, especially for context-store changes
- Review each remaining effect in touched files and leave it only if it clearly bridges to something external
@@ -1,146 +0,0 @@
import type { ToolPart } from "@kilocode/sdk/v2/client"
import type { Page } from "@playwright/test"
import { test, expect } from "../fixtures"
import { assistantText } from "../actions"
import { promptSelector } from "../selectors"
import { createSdk } from "../utils"
const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
type Sdk = ReturnType<typeof createSdk>
const isBash = (part: unknown): part is ToolPart => {
if (!part || typeof part !== "object") return false
if (!("type" in part) || part.type !== "tool") return false
if (!("tool" in part) || part.tool !== "bash") return false
return "state" in part
}
async function wait(page: Page, value: string) {
await expect.poll(async () => text(await page.locator(promptSelector).textContent())).toBe(value)
}
async function reply(sdk: Sdk, sessionID: string, token: string) {
await expect.poll(() => assistantText(sdk, sessionID), { timeout: 90_000 }).toContain(token)
}
async function shell(sdk: Sdk, sessionID: string, cmd: string, token: string) {
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
const part = messages
.filter((item) => item.info.role === "assistant")
.flatMap((item) => item.parts)
.filter(isBash)
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
if (!part || part.state.status !== "completed") return
return typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
},
{ timeout: 90_000 },
)
.toContain(token)
}
test("prompt history restores unsent draft with arrow navigation", async ({ page, project, assistant }) => {
test.setTimeout(120_000)
const firstToken = `E2E_HISTORY_ONE_${Date.now()}`
const secondToken = `E2E_HISTORY_TWO_${Date.now()}`
const first = `Reply with exactly: ${firstToken}`
const second = `Reply with exactly: ${secondToken}`
const draft = `draft ${Date.now()}`
await project.open()
await assistant.reply(firstToken)
const sessionID = await project.prompt(first)
await wait(page, "")
await reply(project.sdk, sessionID, firstToken)
await assistant.reply(secondToken)
await project.prompt(second)
await wait(page, "")
await reply(project.sdk, sessionID, secondToken)
const prompt = page.locator(promptSelector)
await prompt.click()
await page.keyboard.type(draft)
await wait(page, draft)
await prompt.fill("")
await wait(page, "")
await page.keyboard.press("ArrowUp")
await wait(page, second)
await page.keyboard.press("ArrowUp")
await wait(page, first)
await page.keyboard.press("ArrowDown")
await wait(page, second)
await page.keyboard.press("ArrowDown")
await wait(page, "")
})
test.fixme("shell history stays separate from normal prompt history", async ({ page, sdk, gotoSession }) => {
test.setTimeout(120_000)
const firstToken = `E2E_SHELL_ONE_${Date.now()}`
const secondToken = `E2E_SHELL_TWO_${Date.now()}`
const normalToken = `E2E_NORMAL_${Date.now()}`
const first = `echo ${firstToken}`
const second = `echo ${secondToken}`
const normal = `Reply with exactly: ${normalToken}`
await gotoSession()
const prompt = page.locator(promptSelector)
await prompt.click()
await page.keyboard.type("!")
await page.keyboard.type(first)
await page.keyboard.press("Enter")
await wait(page, "")
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
const sessionID = sessionIDFromUrl(page.url())!
await shell(sdk, sessionID, first, firstToken)
await prompt.click()
await page.keyboard.type("!")
await page.keyboard.type(second)
await page.keyboard.press("Enter")
await wait(page, "")
await shell(sdk, sessionID, second, secondToken)
await page.keyboard.press("Escape")
await wait(page, "")
await prompt.click()
await page.keyboard.type("!")
await page.keyboard.press("ArrowUp")
await wait(page, second)
await page.keyboard.press("ArrowUp")
await wait(page, first)
await page.keyboard.press("ArrowDown")
await wait(page, second)
await page.keyboard.press("ArrowDown")
await wait(page, "")
await page.keyboard.press("Escape")
await wait(page, "")
await prompt.click()
await page.keyboard.type(normal)
await page.keyboard.press("Enter")
await wait(page, "")
await reply(sdk, sessionID, normalToken)
await prompt.click()
await page.keyboard.press("ArrowUp")
await wait(page, normal)
})
@@ -1,74 +0,0 @@
import type { ToolPart } from "@kilocode/sdk/v2/client"
import { test, expect } from "../fixtures"
import { closeDialog, openSettings, withSession } from "../actions"
import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors"
const isBash = (part: unknown): part is ToolPart => {
if (!part || typeof part !== "object") return false
if (!("type" in part) || part.type !== "tool") return false
if (!("tool" in part) || part.tool !== "bash") return false
return "state" in part
}
test("shell mode runs a command in the project directory", async ({ page, project }) => {
test.setTimeout(120_000)
await project.open()
const cmd = process.platform === "win32" ? "dir" : "command ls"
await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => {
project.trackSession(session.id)
await project.gotoSession(session.id)
const dialog = await openSettings(page)
const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first()
const input = toggle.locator('[data-slot="switch-input"]').first()
await expect(toggle).toBeVisible()
if ((await input.getAttribute("aria-checked")) !== "true") {
await toggle.locator('[data-slot="switch-control"]').click()
await expect(input).toHaveAttribute("aria-checked", "true")
}
await closeDialog(page, dialog)
await project.shell(cmd)
await expect
.poll(
async () => {
const list = await project.sdk.session
.messages({ sessionID: session.id, limit: 50 })
.then((x) => x.data ?? [])
const msg = list.findLast(
(item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === project.directory,
)
if (!msg) return
const part = msg.parts
.filter(isBash)
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
if (!part || part.state.status !== "completed") return
const output =
typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
if (!output.includes("README.md")) return
return { cwd: project.directory, output }
},
{ timeout: 90_000 },
)
.toEqual(expect.objectContaining({ cwd: project.directory, output: expect.stringContaining("README.md") }))
})
})
test("shell mode unmounts model and variant controls", async ({ page, project }) => {
await project.open()
const prompt = page.locator(promptSelector).first()
await expect(page.locator(promptModelSelector)).toHaveCount(1)
await expect(page.locator(promptVariantSelector)).toHaveCount(1)
await prompt.click()
await page.keyboard.type("!")
await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
await expect(page.locator(promptModelSelector)).toHaveCount(0)
await expect(page.locator(promptVariantSelector)).toHaveCount(0)
})
@@ -1,66 +0,0 @@
import { test, expect } from "../fixtures"
import { promptSelector } from "../selectors"
import { withSession } from "../actions"
const shareDisabled = process.env.KILO_DISABLE_SHARE === "true" || process.env.KILO_DISABLE_SHARE === "1"
async function seed(sdk: Parameters<typeof withSession>[0], sessionID: string) {
await sdk.session.promptAsync({
sessionID,
noReply: true,
parts: [{ type: "text", text: "e2e share seed" }],
})
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
return messages.length
},
{ timeout: 30_000 },
)
.toBeGreaterThan(0)
}
test("/share and /unshare update session share state", async ({ page, project }) => {
test.skip(shareDisabled, "Share is disabled in this environment (KILO_DISABLE_SHARE).")
await project.open()
await withSession(project.sdk, `e2e slash share ${Date.now()}`, async (session) => {
project.trackSession(session.id)
const prompt = page.locator(promptSelector)
await seed(project.sdk, session.id)
await project.gotoSession(session.id)
await prompt.click()
await page.keyboard.type("/share")
await expect(page.locator('[data-slash-id="session.share"]').first()).toBeVisible()
await page.keyboard.press("Enter")
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.share?.url || undefined
},
{ timeout: 30_000 },
)
.not.toBeUndefined()
await prompt.click()
await page.keyboard.type("/unshare")
await expect(page.locator('[data-slash-id="session.unshare"]').first()).toBeVisible()
await page.keyboard.press("Enter")
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.share?.url || undefined
},
{ timeout: 30_000 },
)
.toBeUndefined()
})
})
-182
View File
@@ -1,182 +0,0 @@
import { test, expect } from "../fixtures"
import {
openSidebar,
openSessionMoreMenu,
clickMenuItem,
confirmDialog,
openSharePopover,
withSession,
} from "../actions"
import { sessionItemSelector, inlineInputSelector } from "../selectors"
const shareDisabled = process.env.KILO_DISABLE_SHARE === "true" || process.env.KILO_DISABLE_SHARE === "1"
type Sdk = Parameters<typeof withSession>[0]
async function seedMessage(sdk: Sdk, sessionID: string) {
await sdk.session.promptAsync({
sessionID,
noReply: true,
parts: [{ type: "text", text: "e2e seed" }],
})
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
return messages.length
},
{ timeout: 30_000 },
)
.toBeGreaterThan(0)
}
test("session can be renamed via header menu", async ({ page, project }) => {
const stamp = Date.now()
const originalTitle = `e2e rename test ${stamp}`
const renamedTitle = `e2e renamed ${stamp}`
await project.open()
await withSession(project.sdk, originalTitle, async (session) => {
project.trackSession(session.id)
await seedMessage(project.sdk, session.id)
await project.gotoSession(session.id)
await expect(page.getByRole("heading", { level: 1 }).first()).toHaveText(originalTitle)
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /rename/i)
const input = page.locator(".scroll-view__viewport").locator(inlineInputSelector).first()
await expect(input).toBeVisible()
await expect(input).toBeFocused()
await input.fill(renamedTitle)
await expect(input).toHaveValue(renamedTitle)
await input.press("Enter")
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.title
},
{ timeout: 30_000 },
)
.toBe(renamedTitle)
await expect(page.getByRole("heading", { level: 1 }).first()).toHaveText(renamedTitle)
})
})
test("session can be archived via header menu", async ({ page, project }) => {
const stamp = Date.now()
const title = `e2e archive test ${stamp}`
await project.open()
await withSession(project.sdk, title, async (session) => {
project.trackSession(session.id)
await seedMessage(project.sdk, session.id)
await project.gotoSession(session.id)
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /archive/i)
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.time?.archived
},
{ timeout: 30_000 },
)
.not.toBeUndefined()
await openSidebar(page)
await expect(page.locator(sessionItemSelector(session.id))).toHaveCount(0)
})
})
test("session can be deleted via header menu", async ({ page, project }) => {
const stamp = Date.now()
const title = `e2e delete test ${stamp}`
await project.open()
await withSession(project.sdk, title, async (session) => {
project.trackSession(session.id)
await seedMessage(project.sdk, session.id)
await project.gotoSession(session.id)
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /delete/i)
await confirmDialog(page, /delete/i)
await expect
.poll(
async () => {
const data = await project.sdk.session
.get({ sessionID: session.id })
.then((r) => r.data)
.catch(() => undefined)
return data?.id
},
{ timeout: 30_000 },
)
.toBeUndefined()
await openSidebar(page)
await expect(page.locator(sessionItemSelector(session.id))).toHaveCount(0)
})
})
test("session can be shared and unshared via header button", async ({ page, project }) => {
test.skip(shareDisabled, "Share is disabled in this environment (KILO_DISABLE_SHARE).")
const stamp = Date.now()
const title = `e2e share test ${stamp}`
await project.open()
await withSession(project.sdk, title, async (session) => {
project.trackSession(session.id)
await project.gotoSession(session.id)
await project.prompt(`share seed ${stamp}`)
const shared = await openSharePopover(page)
const publish = shared.popoverBody.getByRole("button", { name: "Publish" }).first()
await expect(publish).toBeVisible({ timeout: 30_000 })
await publish.click()
await expect(shared.popoverBody.getByRole("button", { name: "Unpublish" }).first()).toBeVisible({
timeout: 30_000,
})
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.share?.url || undefined
},
{ timeout: 30_000 },
)
.not.toBeUndefined()
const unpublish = shared.popoverBody.getByRole("button", { name: "Unpublish" }).first()
await expect(unpublish).toBeVisible({ timeout: 30_000 })
await unpublish.click()
await expect(shared.popoverBody.getByRole("button", { name: "Publish" }).first()).toBeVisible({
timeout: 30_000,
})
await expect
.poll(
async () => {
const data = await project.sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.share?.url || undefined
},
{ timeout: 30_000 },
)
.toBeUndefined()
const unshared = await openSharePopover(page)
await expect(unshared.popoverBody.getByRole("button", { name: "Publish" }).first()).toBeVisible({
timeout: 30_000,
})
})
})
-11
View File
@@ -1,11 +0,0 @@
import { test } from "@playwright/test"
test(
"test something cool",
{
annotation: { type: "todo" },
},
async () => {
test.fixme()
},
)
-9
View File
@@ -1,9 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": "..",
"types": ["node", "bun"]
},
"include": ["./**/*.ts"]
}
-75
View File
@@ -1,75 +0,0 @@
import { GlobalRegistrator } from "@happy-dom/global-registrator"
GlobalRegistrator.register()
const originalGetContext = HTMLCanvasElement.prototype.getContext
// @ts-expect-error - we're overriding with a simplified mock
HTMLCanvasElement.prototype.getContext = function (contextType: string, _options?: unknown) {
if (contextType === "2d") {
return {
canvas: this,
fillStyle: "#000000",
strokeStyle: "#000000",
font: "12px monospace",
textAlign: "start",
textBaseline: "alphabetic",
globalAlpha: 1,
globalCompositeOperation: "source-over",
imageSmoothingEnabled: true,
lineWidth: 1,
lineCap: "butt",
lineJoin: "miter",
miterLimit: 10,
shadowBlur: 0,
shadowColor: "rgba(0, 0, 0, 0)",
shadowOffsetX: 0,
shadowOffsetY: 0,
fillRect: () => {},
strokeRect: () => {},
clearRect: () => {},
fillText: () => {},
strokeText: () => {},
measureText: (text: string) => ({ width: text.length * 8 }),
drawImage: () => {},
save: () => {},
restore: () => {},
scale: () => {},
rotate: () => {},
translate: () => {},
transform: () => {},
setTransform: () => {},
resetTransform: () => {},
createLinearGradient: () => ({ addColorStop: () => {} }),
createRadialGradient: () => ({ addColorStop: () => {} }),
createPattern: () => null,
beginPath: () => {},
closePath: () => {},
moveTo: () => {},
lineTo: () => {},
bezierCurveTo: () => {},
quadraticCurveTo: () => {},
arc: () => {},
arcTo: () => {},
ellipse: () => {},
rect: () => {},
fill: () => {},
stroke: () => {},
clip: () => {},
isPointInPath: () => false,
isPointInStroke: () => false,
getTransform: () => ({}),
getImageData: () => ({
data: new Uint8ClampedArray(0),
width: 0,
height: 0,
}),
putImageData: () => {},
createImageData: () => ({
data: new Uint8ClampedArray(0),
width: 0,
height: 0,
}),
} as unknown as CanvasRenderingContext2D
}
return originalGetContext.call(this, contextType as "2d", _options)
}
-23
View File
@@ -1,23 +0,0 @@
<!doctype html>
<html lang="en" style="background-color: var(--background-base)">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" />
<title>OpenCode</title>
<link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
<link rel="shortcut icon" href="/favicon-v3.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v3.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#F8F7F7" />
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
</head>
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="flex flex-col h-dvh p-px"></div>
<script src="/src/entry.tsx" type="module"></script>
</body>
</html>
-80
View File
@@ -1,80 +0,0 @@
{
"name": "@opencode-ai/app",
"version": "7.2.31",
"description": "",
"type": "module",
"exports": {
".": "./src/index.ts",
"./vite": "./vite.js",
"./index.css": "./src/index.css"
},
"scripts": {
"typecheck": "tsgo -b",
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"test": "bun run test:unit",
"test:ci": "mkdir -p .artifacts/unit && bun test --preload ./happydom.ts ./src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
"test:unit": "bun test --preload ./happydom.ts ./src",
"test:unit:watch": "bun test --watch --preload ./happydom.ts ./src",
"test:e2e": "playwright test",
"test:e2e:local": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report e2e/playwright-report"
},
"license": "MIT",
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "catalog:",
"@tailwindcss/vite": "catalog:",
"@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:",
"@types/luxon": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
"vite-plugin-solid": "catalog:"
},
"dependencies": {
"@kobalte/core": "catalog:",
"@kilocode/sdk": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/shared": "workspace:*",
"@shikijs/transformers": "3.9.2",
"@solid-primitives/active-element": "2.1.3",
"@solid-primitives/audio": "1.4.2",
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/event-listener": "2.4.5",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.5",
"@solid-primitives/scroll": "2.1.3",
"@solid-primitives/storage": "catalog:",
"@solid-primitives/timer": "1.4.4",
"@solid-primitives/websocket": "1.3.1",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
"@tanstack/solid-query": "5.91.4",
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"effect": "catalog:",
"fuzzysort": "catalog:",
"ghostty-web": "github:anomalyco/ghostty-web#main",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
"remeda": "catalog:",
"shiki": "catalog:",
"solid-js": "catalog:",
"solid-list": "catalog:",
"tailwindcss": "catalog:",
"virtua": "catalog:",
"zod": "catalog:",
"@kilocode/kilo-ui": "workspace:*",
"@kilocode/kilo-i18n": "workspace:*"
},
"peerDependencies": {}
}
-50
View File
@@ -1,50 +0,0 @@
import { defineConfig, devices } from "@playwright/test"
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
const reuse = !process.env.CI
const workers = Number(process.env.PLAYWRIGHT_WORKERS ?? (process.env.CI ? 5 : 0)) || undefined
const reporter = [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]] as const
if (process.env.PLAYWRIGHT_JUNIT_OUTPUT) {
reporter.push(["junit", { outputFile: process.env.PLAYWRIGHT_JUNIT_OUTPUT }])
}
export default defineConfig({
testDir: "./e2e",
outputDir: "./e2e/test-results",
timeout: 60_000,
expect: {
timeout: 10_000,
},
fullyParallel: process.env.PLAYWRIGHT_FULLY_PARALLEL === "1",
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers,
reporter,
webServer: {
command,
url: baseURL,
reuseExistingServer: reuse,
timeout: 120_000,
env: {
VITE_KILO_SERVER_HOST: serverHost,
VITE_KILO_SERVER_PORT: serverPort,
},
},
use: {
baseURL,
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
})
-17
View File
@@ -1,17 +0,0 @@
/assets/*.js
Content-Type: application/javascript
/assets/*.mjs
Content-Type: application/javascript
/assets/*.css
Content-Type: text/css
/*.js
Content-Type: application/javascript
/*.mjs
Content-Type: application/javascript
/*.css
Content-Type: text/css
@@ -1 +0,0 @@
../../ui/src/assets/favicon/apple-touch-icon-v3.png
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/apple-touch-icon.png
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-96x96-v3.png
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-96x96.png
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-v3.ico
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon-v3.svg
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon.ico
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/favicon.svg
-35
View File
@@ -1,35 +0,0 @@
;(function () {
var key = "opencode-theme-id"
var themeId = localStorage.getItem(key) || "oc-2"
if (themeId === "oc-1") {
themeId = "oc-2"
localStorage.setItem(key, themeId)
localStorage.removeItem("opencode-theme-css-light")
localStorage.removeItem("opencode-theme-css-dark")
}
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
var mode = isDark ? "dark" : "light"
document.documentElement.dataset.theme = themeId
document.documentElement.dataset.colorScheme = mode
if (themeId === "oc-2") return
var css = localStorage.getItem("opencode-theme-css-" + mode)
if (css) {
var style = document.createElement("style")
style.id = "oc-theme-preload"
style.textContent =
":root{color-scheme:" +
mode +
";--text-mix-blend-mode:" +
(isDark ? "plus-lighter" : "multiply") +
";" +
css +
"}"
document.head.appendChild(style)
}
})()
-1
View File
@@ -1 +0,0 @@
../../ui/src/assets/favicon/site.webmanifest

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