* perf(vscode): stop O(N) reactive cascade on every streaming token
The webview DataBridge wrapped the whole session Data shape in a
`createMemo`, whose body walked `store.parts[msg.id]` for every message
in the session family. Any single part mutation — i.e. every token
delta — invalidated the memo, produced a fresh POJO, and invalidated
every downstream consumer (including O(N) scans inside each mounted
SessionTurn). On a 200-message session a Chrome CPU profile showed
three back-to-back 440ms main-thread blocks per SSE batch, ~46% of
the time in Solid reactive runtime alone.
Expose `data` as a plain object with reactive getters over
`session.allMessages`/`allParts`/`allStatusMap` so consumers reading
`data.store.part[Y]` subscribe to only that key. Removes the now-unused
`familyData` helper and its interface/mock entries.
* perf(ui): drop TextShimmer JS timer — CSS-only animation
A createEffect inside TextShimmer ran clearTimeout + setTimeout on every
`active` prop change to gate the sweep animation via a `data-run`
attribute. During LLM token streaming in long sessions, `active` props
(bound to `pending()` / `running()` accessors) thrashed as tools
started/finished across many shimmer instances. CPU profile of a 7s
streaming window showed ~2,500 timer operations — 16% of the blocked
main-thread time.
Remove the effect and drive the animation purely from the `data-active`
attribute. The opacity transition on the shimmer char (220ms) already
handles the fade, so visual behavior is unchanged. Adds one static
regression guard and one runtime perf assertion (with happy-dom) that
toggling the prop 1000 times results in zero timer calls.
* perf(kilo-ui): skip layout reads in GrowBox watch-mode ResizeObserver
The GrowBox component wraps each assistant part and, when watch=true
(which is set on the currently-streaming text part), runs a
ResizeObserver that called body.getBoundingClientRect() via
targetHeight() on every body-size change. During streaming this fires
at ~60Hz and each call forces a synchronous layout. CPU profile of a
7s streaming window showed 1,362 gBCR samples (~9% of blocked
main-thread time) all attributable to this path.
Reuse the browser's pre-measured contentBoxSize / contentRect from the
observer entries — no extra layout read. Also skip sub-pixel updates
(<2px) that the spring absorbs imperceptibly anyway, cutting per-token
spring work when tokens add tiny height deltas.
* perf(ui): coalesce markdown parse to one per animation frame
During LLM token streaming, the Markdown render effect ran
temp.innerHTML = content + morphdom on every content update. SSE
tokens arrive at 60–200Hz and each delta reparsed the entire
accumulated HTML. CPU profile of a 7s streaming window showed 2,940
ParseHTML events totaling ~619ms (~46% of blocked main-thread time).
Queue the latest content in a component-scoped pending variable and
run the morphdom pass inside requestAnimationFrame. K rapid updates
before the frame fires now collapse to one parse. The onCleanup
handler cancels any queued frame so it doesn't touch an unmounted
DOM. Fast-path is preserved untouched so non-streaming first paint
stays synchronous.
* chore(changeset): consolidate streaming-perf changesets into one
Per-commit changesets produced four nearly-identical release-note
entries. The user-visible change is a single perceptual improvement —
streaming is smooth in long sessions — so roll them up into one
feature-oriented entry.
* test(vscode): consolidate streaming perf tests + wire into CI
Replace three synthetic reactivity tests with a single end-to-end
streaming perf benchmark that:
- Renders the real TextShimmer component and asserts zero setTimeout/
clearTimeout calls during a 100-toggle burst (TextShimmer fix).
- Asserts per-key Solid reactivity: 100 text deltas on one message
must re-run only that message's consumer, not O(N) consumers
(DataBridge cascade fix).
- Uses only count-based assertions against deterministic APIs
(setTimeout/clearTimeout/innerHTML setter) — no wall-clock
thresholds, so it doesn't flake under CI load.
Also wire `bun run test:webview-reactivity` into the test-vscode
workflow so the benchmark runs on every PR that touches
packages/kilo-vscode, packages/ui, or packages/kilo-ui. Without this
wiring the perf regression guard would have shipped dormant.
* test(vscode): declare @happy-dom/global-registrator as devDep
The streaming-perf benchmark imports @happy-dom/global-registrator to
get a DOM for mounting the real TextShimmer component. It resolved
locally through workspace hoisting but CI's clean install didn't have
it. Make the dependency explicit.
* test(vscode): import TextShimmer via package export so JSX resolves
Using the deep relative path (../../../ui/src/components/text-shimmer)
made Bun's test transpiler apply kilo-vscode's tsconfig — which has no
`jsxImportSource` — so the .tsx file was compiled with the default
React runtime, producing "React is not defined" in CI.
Resolving through the package export (@opencode-ai/ui/text-shimmer)
picks up packages/ui/tsconfig.json which sets
`jsxImportSource: solid-js`. Works consistently across Linux/macOS/
Windows CI without needing bunfig-level JSX overrides.
* test(vscode): address bot review — add runtime coverage for Markdown + GrowBox, drop empty smoke test
Two kilo-code-bot findings on the streaming perf bench:
1. The 'benchmark completes quickly' smoke test timed an empty block,
so `elapsed` was always near zero and the assertion never fired.
Drop it — the real benchmarks below already complete in ~90ms.
2. The original file installed spy counters for innerHTML writes and
getBoundingClientRect but never asserted against them, leaving
Markdown rAF coalescing and GrowBox layout-read regressions silently
uncaught.
Add two runtime mirrors:
- Markdown rAF pattern: 100 async content updates coalesce to <20 parses
(would be exactly 100 pre-fix).
- GrowBox ResizeObserver pattern: 100 synthetic resize callbacks using
contentBoxSize/contentRect trigger zero getBoundingClientRect calls
(would be exactly 100 pre-fix).
Source-level regression guards in tests/unit/markdown-raf-coalesce.test.ts
and tests/unit/growbox-no-layout-thrash.test.ts cover the actual
component code. The runtime tests here prove the patterns the guards
require actually deliver the perf property at runtime.
Benchmark runs in ~90ms, 5 consecutive local runs all green.
* test(vscode): drop unstable TextShimmer runtime mount from streaming perf bench
The benchmark tried to mount the real @opencode-ai/ui TextShimmer to
assert zero setTimeout/clearTimeout calls. That required Bun's test
runner to transpile text-shimmer.tsx with Solid's JSX runtime, which
depends on tsconfig resolution walking up to packages/ui/tsconfig.json.
In CI (fresh workspace, different node_modules layout) this resolution
was unstable and kept falling back to React JSX ("React is not
defined").
Keep the three runtime patterns that don't need JSX transpilation
(DataBridge cascade, Markdown rAF coalescing, GrowBox contentRect),
plus the source-level regression guard at
tests/unit/textshimmer-no-timer.test.ts which asserts text-shimmer.tsx
contains no setTimeout/clearTimeout/createEffect/data-run. Together
these cover all four fixes without CI flakiness.
5 consecutive local runs pass in ~80ms.
* test(vscode): remove streaming perf tests
Static source-parsing guards and pattern-mirror runtime tests didn't
actually exercise the fixed component code — a regression in the real
code could have left them green. Remove them along with the
test:webview-reactivity script, the workflow step, the
@happy-dom/global-registrator devDep, and the tests/webview-reactivity
directory. The four perf fixes stand on their own; adding dubious
guards was worse than adding none.
* test(vscode): restore static perf-regression guards wired to real source
Restore four guards that each parse the actual fixed component source
and fail loudly if the fix pattern is removed:
- databridge-shape.test.ts reads webview-ui/src/App.tsx, asserts
`data` is not wrapped in createMemo
- textshimmer-no-timer.test.ts reads ui/src/components/text-shimmer.tsx
+ .css, asserts no setTimeout/
clearTimeout/createEffect, animation
gated on data-active
- markdown-raf-coalesce.test.ts reads ui/src/components/markdown.tsx,
asserts the render createEffect uses
requestAnimationFrame + cancelAnimationFrame
- growbox-no-layout-thrash.test.ts reads kilo-ui/src/components/grow-box.tsx,
asserts the ResizeObserver callback
does not call gBCR, uses contentRect/
contentBoxSize, and has the sub-pixel
delta guard
Verified by mutation: each guard fails when its fix pattern is removed
from the real source file and passes again once restored. Runs as part
of the existing test:unit script (no extra CI wiring).
On every kilo CLI startup the process writes an error like:
log stream error: ENOENT: no such file or directory, open
'<log-dir>/<log-dir>/.log-history'
Log rotation is effectively disabled because rotating-file-stream cannot
open its history file.
Root cause: rotating-file-stream (3.2.x) treats the `history` option as
a filename resolved against the `path` option. In
`packages/opencode/src/util/log.ts` we were passing an absolute path
(`path.join(dir, '.log-history')`) for `history` while also setting
`path: dir`, so the library concatenated the two and produced the
`<dir>/<dir>/.log-history` path that ENOENTs.
Passing the relative filename `.log-history` lets the library resolve
the history file under the configured `path` directory, matches the
docs, and restores the 50 MB / 10-file rotation cap.
Fixes#8252Fixes#8275Fixes#9321
Click the chevron next to the + tab button and pick 'New Terminal' (or hit Cmd+Shift+T / Ctrl+Shift+T) to spawn a real shell inside the selected worktree or Local directory. Each terminal runs via the Kilo CLI's PTY backend, streamed over a direct loopback WebSocket so raw bytes bypass postMessage. Tabs mirror the worktree split-button pattern, support mixed drag-reorder with session tabs, and survive worktree-context switches without losing xterm state (slots are opacity-toggled in a persistent absolute-positioned layer, never unmounted). The legacy Cmd+/ integrated-terminal shortcut and console icon are preserved so existing muscle memory keeps working.
Single $ delimiters in agent responses (e.g. $93K, $307K) were being
interpreted as inline LaTeX math by marked-katex-extension, garbling
dollar amounts. Override the inlineKatex tokenizer to never match and
remove the regex-based single-dollar rendering in renderMathInText.
Display math ($$...$$) is preserved.
Replace granular submodule imports with their parent barrel index
across snapshot diff source and related test files. This covers
util/log, util/filesystem, config/config, provider/provider,
tool/registry, tool/truncate, and the filesystem shared package.
Increase paste-summary trigger from 3 lines/150 chars to 5 lines/800
chars to reduce unnecessary summarization on small pastes. Add a branded
reauthentication hint when GitHub Copilot returns 403. Guard against
empty agent list during org switch in the model auto-update effect and
reformat multi-line ternary for readability. Relocate inline comment on
mercury exclusion in reasoning effort variants.
Replaces the number-prefixed question-picker block with a full-width tinted
row: icon + suggestion text on the left, clickable action buttons on the
right. Removes keyboard shortcuts and the esc-dismiss hint -- dismissal
already happens automatically server-side when the user sends a new prompt.
Previously, the non-blocking inline SuggestBar never showed up because the
Suggest component used an early-return if/else that ran once at mount
(when no pending request existed yet) and never re-evaluated when the
suggestion arrived. Users saw 'Suggesting next step...' forever with no
way to accept or dismiss, which felt like the suggestion was blocking
the session again. Switching to <Switch>/<Match> so the branch updates
when the request lands makes the bar appear and be interactive.
Replace custom timeout() and stream() helpers with platform
AbortSignal.any + AbortSignal.timeout (available since Node 20.3).
Eliminates ~50 lines of manual signal management and stream wrapping.
Parallel bun test subprocesses can race between isPortInUse() and
listen(), causing the loser to crash with EADDRINUSE. Treat that error
as 'another instance owns the port' (same semantics as the isPortInUse
branch above) so the auto-connect test stops flaking in CI.
Extract inline Opus 4.7 adaptive effort logic into anthropicAdaptiveEfforts
helper, extend smallOptions to recognize Kilo Gateway alongside OpenRouter,
fix LSP test file path from lsp/index.ts to lsp/lsp.ts, and update xhigh
variant assertion to include display: "summarized".
Swap manual save-and-restore of Config.get for bun:test spyOn/mock.restore
across session-list and recall test suites, ensuring proper mock teardown
and consistent namespace imports.
Correct source comment annotations to reflect relocated modules:
- config.ts → config/model-id.ts for model schema link
- config/tui-migrate.ts → cli/cmd/tui/config/tui-migrate.ts for tui.json link
Alphabetically sort import statements in the SDK codegen output and
reposition type declarations (EventInstallationUpdated,
EventSessionTurnOpen, SessionStatus, Todo, etc.) to match updated
code generation ordering. In the server instance router, assign the
chained route builder to a `full` variable before passing it to
`registerKiloRoutes` for clarity.
Moves the 'Run review?' suggestion picker out of the footer bar above the
prompt and into the conversation itself, at the position of the suggest
tool call. Frees up vertical space for reading while scrolling and
matches where the VS Code extension shows the same picker. Clicking an
option still accepts, digit keys 1/2 fast-accept when the prompt isn't
focused, and Esc dismisses. Blocking suggestions keep the above-prompt
overlay.
When every compaction round still overflowed the model context, SessionPrompt.runLoop would keep calling compaction forever and report the turn as completed. Cap attempts at three per turn and surface exhaustion as a ContextOverflowError on the assistant message with TurnClose reason=error.
Bump @opentelemetry/core, sdk-trace-base, sdk-trace-node, and
resources from 1.30.x to 2.6.1 and semantic-conventions from 1.28.0
to 1.40.0 in kilo-telemetry. Migrate to the new v2 API surface:
replace `new Resource()` with `resourceFromAttributes()`, rename
`parentSpanId` to `parentSpanContext`, and rename
`instrumentationLibrary` to `instrumentationScope` in span types and
tests. Alphabetically sort dependency entries in the opencode
package.json.
Remove synchronous Env.get/Env.all calls in provider, tool registry,
and model-cache modules, replacing them with effect-yielded env
lookups or direct process.env reads where appropriate. Thread resolved
env through patchCustomLoaderResult and kiloCustomLoaders dependency
type. Drop unused paste-summary import from prompt component, fix
createResource action call in session list dialog, add bell toggle KV
signal, and rewrite config-gitignore test to use proper Effect layers
instead of mocked AppRuntime. Update remaining import paths and test
module mocks to align with barrel re-exports.