Skip to content

Instantly share code, notes, and snippets.

@CriDos
Created June 19, 2026 20:38
Show Gist options
  • Select an option

  • Save CriDos/b7cddd4662ef7cbbecc4d4e68ae7aa4a to your computer and use it in GitHub Desktop.

Select an option

Save CriDos/b7cddd4662ef7cbbecc4d4e68ae7aa4a to your computer and use it in GitHub Desktop.
patch-kilo-autoscroll.ps1
<#
.SYNOPSIS
Patch the installed Kilo Code VS Code extension to fix the chat auto-scroll
"can't detach from the latest message" bug.
.DESCRIPTION
Two minimal fixes are applied to the minified webview bundles:
1. Wheel-up input now records an interaction timestamp so handleScroll's
near-bottom re-attach cannot immediately undo the pause.
2. The near-bottom re-attach branch no longer clears userScrolled while the
user just scrolled up.
Menu: apply patch, check status, restore backup, quit.
Backups are written as <file>.bak next to each patched bundle.
An extension update overwrites the bundles, so re-apply after updates.
.PARAMETER ExtDir
Optional explicit path to the installed extension folder
(the one containing dist\webview.js). If omitted, the newest
kilocode.kilo-code-* under .vscode\extensions / .vscode-insiders\extensions
is auto-detected.
#>
param(
[string]$ExtDir,
[ValidateSet('apply', 'status', 'restore')]
[string]$Action
)
$ErrorActionPreference = 'Stop'
# --- patch definitions -------------------------------------------------------
$fixes = @(
@{
name = 'wheel-up timestamp'
orig = 'i.deltaY>=0||e.onWheelUp()'
patched = 'i.deltaY>=0||(n=performance.now(),e.onWheelUp())'
},
@{
name = 'near-bottom re-attach guard'
orig = 'o.userScrolled&&s("userScrolled",!1);return'
patched = 'o.userScrolled&&!h.isRecent()&&s("userScrolled",!1);return'
}
)
# --- helpers ----------------------------------------------------------------
function Find-ExtDir {
if ($ExtDir) {
if (Test-Path -LiteralPath (Join-Path $ExtDir 'dist\webview.js')) { return $ExtDir }
throw "ExtDir '$ExtDir' does not contain dist\webview.js."
}
$roots = @(
(Join-Path $env:USERPROFILE '.vscode\extensions'),
(Join-Path $env:USERPROFILE '.vscode-insiders\extensions')
)
$found = @()
foreach ($r in $roots) {
if (Test-Path -LiteralPath $r) {
$found += Get-ChildItem -LiteralPath $r -Directory -Filter 'kilocode.kilo-code-*' -ErrorAction SilentlyContinue
}
}
if ($found.Count -eq 0) { throw 'No kilocode.kilo-code-* extension folder found.' }
# newest version folder name wins
return ($found | Sort-Object Name -Descending | Select-Object -First 1).FullName
}
function Get-Targets {
param([string]$dir)
$dist = Join-Path $dir 'dist'
if (!(Test-Path -LiteralPath $dist)) { throw "No dist folder in '$dir'." }
# patch wherever any patch marker (orig or patched) lives
Get-ChildItem -LiteralPath $dist -Filter '*.js' -File | Where-Object {
$c = [IO.File]::ReadAllText($_.FullName)
($fixes | ForEach-Object { $_.orig, $_.patched } | Where-Object { $c.Contains($_) }).Count -gt 0
}
}
function Get-FileState {
param([IO.FileInfo]$file)
$c = [IO.File]::ReadAllText($file.FullName)
$state = [ordered]@{ file = $file.Name; orig = 0; patched = 0 }
foreach ($f in $fixes) {
if ($c.Contains($f.orig)) { $state.orig++ }
if ($c.Contains($f.patched)) { $state.patched++ }
}
if ($state.patched -gt 0 -and $state.orig -eq 0) { $state.status = 'patched' }
elseif ($state.patched -gt 0) { $state.status = 'partial' }
elseif ($state.orig -gt 0) { $state.status = 'original' }
else { $state.status = 'n/a' }
return [pscustomobject]$state
}
function Invoke-Apply {
param([IO.FileInfo[]]$targets)
$applied = 0
foreach ($t in $targets) {
$bak = "$($t.FullName).bak"
if (!(Test-Path -LiteralPath $bak)) {
Copy-Item -LiteralPath $t.FullName -Destination $bak
}
$c = [IO.File]::ReadAllText($t.FullName)
$changed = $false
foreach ($f in $fixes) {
if ($c.Contains($f.patched)) { continue } # already applied
if (!$c.Contains($f.orig)) { continue } # not present in this bundle
$c = $c.Replace($f.orig, $f.patched)
$changed = $true
Write-Host " [$($t.Name)] applied: $($f.name)"
}
if ($changed) {
[IO.File]::WriteAllText($t.FullName, $c)
$applied++
} else {
Write-Host " [$($t.Name)] already patched or no pattern - skipped"
}
}
if ($applied -gt 0) {
Write-Host ''
Write-Host 'Done. Reload the VS Code window for the change to take effect' -ForegroundColor Green
Write-Host '(Developer: Reload Window, or close and reopen).'
}
}
function Invoke-Restore {
param([IO.FileInfo[]]$targets)
$restored = 0
foreach ($t in $targets) {
$bak = "$($t.FullName).bak"
if (Test-Path -LiteralPath $bak) {
Copy-Item -LiteralPath $bak -Destination $t.FullName -Force
Write-Host " [$($t.Name)] restored from backup"
$restored++
} else {
Write-Host " [$($t.Name)] no backup found - skipped"
}
}
if ($restored -gt 0) {
Write-Host ''
Write-Host 'Restored. Reload the VS Code window.' -ForegroundColor Green
}
}
function Show-Status {
param([IO.FileInfo[]]$targets)
if ($targets.Count -eq 0) {
Write-Host 'No patchable bundles found in this extension.' -ForegroundColor Yellow
return
}
$rows = foreach ($t in $targets) {
$s = Get-FileState -file $t
$color = switch ($s.status) { 'patched' { 'Green' } 'original' { 'Yellow' } 'partial' { 'Red' } default { 'DarkGray' } }
[pscustomobject]@{
File = $s.file
Status = $s.status
Orig = $s.orig
Patched = $s.patched
}
}
$rows | Format-Table -AutoSize
$summary = ($rows | Group-Object Status | ForEach-Object { "$($_.Name):$($_.Count)" }) -join ' '
Write-Host "Summary: $summary"
}
# --- main menu --------------------------------------------------------------
try {
$dir = Find-ExtDir
Write-Host "Extension: $dir" -ForegroundColor Cyan
$targets = @(Get-Targets -dir $dir)
if ($targets.Count -eq 0) {
Write-Host 'No bundles containing the auto-scroll pattern were found.' -ForegroundColor Yellow
Write-Host 'The installed version may already be fixed or use different code.'
return
}
Write-Host "Patchable bundles: $($targets.Count)"
Write-Host ''
if ($Action) {
switch ($Action) {
'apply' { Invoke-Apply -targets $targets }
'status' { Show-Status -targets $targets }
'restore' { Invoke-Restore -targets $targets }
}
return
}
while ($true) {
Write-Host '--- Kilo Code auto-scroll patch ---'
Write-Host ' 1) Apply patch'
Write-Host ' 2) Check status'
Write-Host ' 3) Restore from backup'
Write-Host ' 4) Quit'
$choice = Read-Host 'Select [1-4]'
switch ($choice) {
'1' { Invoke-Apply -targets $targets }
'2' { Show-Status -targets $targets }
'3' { Invoke-Restore -targets $targets }
'4' { break }
default { Write-Host 'Invalid choice.' -ForegroundColor Yellow }
}
Write-Host ''
if ($choice -eq '4') { break }
}
}
catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment