From 2613ff93c6446eb3a662ed6d4a4b063131a6a371 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 4 Aug 2026 10:11:48 +0200 Subject: [PATCH 1/2] perf(vscode): defer agent manager terminal addons --- .changeset/faster-agent-manager-terminal.md | 5 ++ .../agent-manager/terminal/TerminalTab.tsx | 65 +++++++++---------- 2 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 .changeset/faster-agent-manager-terminal.md diff --git a/.changeset/faster-agent-manager-terminal.md b/.changeset/faster-agent-manager-terminal.md new file mode 100644 index 00000000000..d16d0263fd9 --- /dev/null +++ b/.changeset/faster-agent-manager-terminal.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show the first Agent Manager terminal prompt sooner by deferring optional terminal enhancements until after initial paint. diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx index 8ae231cacb4..94b034d92a0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx @@ -158,39 +158,6 @@ export const TerminalTab: Component = (props) => { }) const fit = new FitAddon() term.loadAddon(fit) - // Clickable URLs in terminal output (Cmd/Ctrl+click to open). - // WebLinksAddon's default handler calls `window.open`, which VS Code - // webviews intercept and silently drop โ€” so we pass an explicit - // handler that posts an `openExternal` message. The message falls - // through `AgentManagerProvider.onMessage` to the underlying - // `KiloProvider.handleWebviewMessage` path which already calls - // `vscode.env.openExternal` for sidebar + settings links. - term.loadAddon( - new WebLinksAddon((_event, url) => { - vscode.postMessage({ type: "openExternal", url }) - }), - ) - // OSC 52 clipboard support โ€” lets shell programs (tmux, neovim, etc.) - // copy to the system clipboard via escape sequences. Writes always - // work in the webview; reads require the clipboard-read permission, - // which VS Code does not grant by default, so paste-from-escape - // silently falls back to no-op. Acceptable trade-off. - term.loadAddon(new ClipboardAddon()) - // Unicode 15 grapheme-aware width tables. Fixes cell width for - // emoji introduced in Unicode 12-15 (๐Ÿซ  melting face, ๐Ÿซก salute, - // ๐ŸงŒ troll, and ~400 others) plus ZWJ grapheme sequences like - // ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ and ๐Ÿณ๏ธโ€๐ŸŒˆ. The older `@xterm/addon-unicode11` (which VS - // Code's integrated terminal still uses) stops at Unicode 11 - // (2018), leaving all post-2020 emoji rendered with wrong width โ€” - // the canvas cuts them off in the DOM renderer and cursor math - // drifts by one cell per emoji. VS Code hides this visually with - // WebGL; in a webview we don't have that fallback, so we fix it - // at the buffer-width layer instead. Addon is marked - // "experimental" in its README but has been stable on npm since - // 2023, is shipped by the same maintainer as the core xterm.js - // package, and has no open bugs as of v0.4.0. - term.loadAddon(new UnicodeGraphemesAddon()) - term.unicode.activeVersion = "15-graphemes" term.open(host) // Fit on the next frame โ€” `host` might still have 0px dimensions // during the initial layout pass otherwise. @@ -232,6 +199,8 @@ export const TerminalTab: Component = (props) => { let fallbackTimer: ReturnType | undefined let streamed = false let socketEnded = false + let frame: number | undefined + let next: number | undefined // The failure line must not depend on event ordering: the stream can // close before the exited snapshot lands (fast failures), or stay open // when a background child outlives the script. Write it exactly once, @@ -358,6 +327,34 @@ export const TerminalTab: Component = (props) => { } const disposeData = term.onData(send) open(props.wsUrl) + + // These addons are not needed to paint the initial prompt. Defer them + // until after the first frame so their startup work, especially the + // Unicode 15 width tables, does not delay the shell connection. + const loadAddons = () => { + next = undefined + if (closed) return + + // Clickable URLs in terminal output (Cmd/Ctrl+click to open). + // WebLinksAddon's default handler calls `window.open`, which VS Code + // webviews intercept and silently drop, so post an explicit message. + term.loadAddon( + new WebLinksAddon((_event, url) => { + vscode.postMessage({ type: "openExternal", url }) + }), + ) + // OSC 52 clipboard support for shell programs such as tmux and neovim. + term.loadAddon(new ClipboardAddon()) + // Use grapheme-aware width tables for newer emoji and ZWJ sequences. + term.loadAddon(new UnicodeGraphemesAddon()) + term.unicode.activeVersion = "15-graphemes" + term.refresh(0, Math.max(0, term.rows - 1)) + } + frame = requestAnimationFrame(() => { + frame = undefined + next = requestAnimationFrame(loadAddons) + }) + const restarted = (url: string) => { open(url) } @@ -525,6 +522,8 @@ export const TerminalTab: Component = (props) => { onCleanup(() => { closed = true if (pendingFrame !== null) cancelAnimationFrame(pendingFrame) + if (frame !== undefined) cancelAnimationFrame(frame) + if (next !== undefined) cancelAnimationFrame(next) document.removeEventListener("visibilitychange", onVisibilityChange) window.removeEventListener("focus", onWindowFocus) host.removeEventListener("focusin", onFocusIn) From 6bbbb5dadbe6760ac2ed791922f9164bef2c9ad9 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 4 Aug 2026 10:22:14 +0200 Subject: [PATCH 2/2] fix(vscode): clarify deferred terminal frame handle --- .../webview-ui/agent-manager/terminal/TerminalTab.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx index 94b034d92a0..606c8fa47ec 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx @@ -200,7 +200,7 @@ export const TerminalTab: Component = (props) => { let streamed = false let socketEnded = false let frame: number | undefined - let next: number | undefined + let deferred: number | undefined // The failure line must not depend on event ordering: the stream can // close before the exited snapshot lands (fast failures), or stay open // when a background child outlives the script. Write it exactly once, @@ -332,7 +332,7 @@ export const TerminalTab: Component = (props) => { // until after the first frame so their startup work, especially the // Unicode 15 width tables, does not delay the shell connection. const loadAddons = () => { - next = undefined + deferred = undefined if (closed) return // Clickable URLs in terminal output (Cmd/Ctrl+click to open). @@ -352,7 +352,7 @@ export const TerminalTab: Component = (props) => { } frame = requestAnimationFrame(() => { frame = undefined - next = requestAnimationFrame(loadAddons) + deferred = requestAnimationFrame(loadAddons) }) const restarted = (url: string) => { @@ -523,7 +523,7 @@ export const TerminalTab: Component = (props) => { closed = true if (pendingFrame !== null) cancelAnimationFrame(pendingFrame) if (frame !== undefined) cancelAnimationFrame(frame) - if (next !== undefined) cancelAnimationFrame(next) + if (deferred !== undefined) cancelAnimationFrame(deferred) document.removeEventListener("visibilitychange", onVisibilityChange) window.removeEventListener("focus", onWindowFocus) host.removeEventListener("focusin", onFocusIn)