You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[RFC] Bug fixes + features — WSL support, multi-panel routing, model selector, stop button
👋 Introduction
Hi! I'm @svg153, a software engineer who found this project while looking for a way to use GitHub Copilot directly from the browser side panel without switching context to the terminal.
I've been using the extension in a WSL + Windows setup (WSL runs the native messaging host and Node.js, the browser runs on Windows) and quickly ran into several bugs that prevented it from working at all. I started fixing them one by one and ended up doing a broader pass across the codebase.
Note on tooling: The fixes and features described here were implemented using GitHub Copilot CLI under my direct supervision, with manual testing of every change in my WSL+Windows environment. The separation of the full changes into individual focused PRs was also automated by Copilot CLI. I've reviewed every diff and verified that the isolated branches are clean and correct.
🧪 Full integration test
All changes have been tested together in this PR on my fork:
Symptom: Every tool call fails silently. The LLM attempts to use a browser tool (e.g. get_page_content) and receives "denied-no-approval-rule-and-could-not-request-from-user" from the SDK.
Root cause:@github/copilot-sdk's createSession() defaults to denying all tool permission requests when no permissionHandler is provided.
Fix: Import approveAll from @github/copilot-sdk and pass it as onPermissionRequest: approveAll to createSession(). One line of code, but without it the extension is completely non-functional.
Symptom: All content-script tools (get_page_content, click_element, capture_screenshot, etc.) fail with "No active tab" when the side panel has keyboard focus.
Root cause:chrome.tabs.query({ active: true, currentWindow: true }) returns the extension's own side-panel window when the panel has focus — not the browser tab the user is looking at.
Fix: Use lastFocusedWindow: true instead of currentWindow: true, then filter out chrome-extension://, chrome://, and edge:// URLs. Add a fallback that searches all windows.
Symptom: With multiple browser windows open, responses from one conversation appear in the wrong panel.
Root cause:sendToPanels() broadcasts every response to all connected panel ports.
Fix: Track activePort (the port that sent the last SEND_CHAT_MESSAGE) and route all chat/tool messages only to that port via sendToActivePanel(). Also fixes empty CHAT_RESPONSE payloads and silent tool execution failures.
Symptom: The host fails to start on any platform other than macOS Apple Silicon with a cryptic ENOENT error.
Root cause: Two hardcoded paths: the shebang #!/opt/homebrew/bin/node and cliPath: '/opt/homebrew/bin/copilot' in CopilotClient. Both only exist on macOS+Homebrew+Apple Silicon.
Fix:
Change shebang to #!/usr/bin/env node
Add findCopilotCli() that checks common install locations across all platforms (macOS Homebrew, Linux system/user, Windows), then falls back to which/where
Problem:NativeMessage had fictional variants (COPILOT_REQUEST, COPILOT_RESPONSE, COPILOT_STREAM) not present in the actual protocol. TypeScript provided zero type safety for the native messaging layer. Also: duplicate HOVER_ELEMENT in ContentScriptMessage.
Fix: Replace with HostOutboundMessage (SW→host) and HostInboundMessage (host→SW). NativeMessage kept as @deprecated alias.
Feature: A red stop button (■) replacing the send button while streaming. Calls session.abort(), finalizes the partial response without losing already-received text, resets loading state. Textarea disabled during loading to prevent double-sends.
Feature:<select> dropdown in the header showing available models (fetched on connect via GET_MODELS), allowing mid-session model switching via SET_MODEL. Adds ModelInfo type and full message stack support.
[RFC] Bug fixes + features — WSL support, multi-panel routing, model selector, stop button
👋 Introduction
Hi! I'm @svg153, a software engineer who found this project while looking for a way to use GitHub Copilot directly from the browser side panel without switching context to the terminal.
I've been using the extension in a WSL + Windows setup (WSL runs the native messaging host and Node.js, the browser runs on Windows) and quickly ran into several bugs that prevented it from working at all. I started fixing them one by one and ended up doing a broader pass across the codebase.
All the work has been done on a fork of this repo:
svg153/github-copilot-browser.🧪 Full integration test
All changes have been tested together in this PR on my fork:
If you want to verify everything works end-to-end before considering individual PRs, that's the place to look.
📦 Individual PRs — one change per PR
I've also split every change into isolated, focused PRs targeting
mainon my fork, so you can cherry-pick exactly what you want:host.mjslastFocusedWindowfor active tab detection in side paneltab-manager.tsservice-worker.tsnative-messaging.tshost.mjsNativeMessagetype with correct host protocol typesmessages.tsisStreamingtoChatMessagetype, remove unsafe caststypes.ts,App.tsx,MessageList.tsxChatInput.tsx,App.tsx,service-worker.ts,host.mjstypes.ts,messages.ts,service-worker.ts,host.mjs,App.tsx,HeaderBar.tsx,copilot-client.ts🔍 Why each change was needed
🐛 PR-9
fix/tool-permission-approve-all— CriticalSymptom: Every tool call fails silently. The LLM attempts to use a browser tool (e.g.
get_page_content) and receives"denied-no-approval-rule-and-could-not-request-from-user"from the SDK.Root cause:
@github/copilot-sdk'screateSession()defaults to denying all tool permission requests when nopermissionHandleris provided.Fix: Import
approveAllfrom@github/copilot-sdkand pass it asonPermissionRequest: approveAlltocreateSession(). One line of code, but without it the extension is completely non-functional.🐛 PR-2
fix/active-tab-side-panel— CriticalSymptom: All content-script tools (
get_page_content,click_element,capture_screenshot, etc.) fail with "No active tab" when the side panel has keyboard focus.Root cause:
chrome.tabs.query({ active: true, currentWindow: true })returns the extension's own side-panel window when the panel has focus — not the browser tab the user is looking at.Fix: Use
lastFocusedWindow: trueinstead ofcurrentWindow: true, then filter outchrome-extension://,chrome://, andedge://URLs. Add a fallback that searches all windows.🐛 PR-6
fix/multi-panel-message-routing— HighSymptom: With multiple browser windows open, responses from one conversation appear in the wrong panel.
Root cause:
sendToPanels()broadcasts every response to all connected panel ports.Fix: Track
activePort(the port that sent the lastSEND_CHAT_MESSAGE) and route all chat/tool messages only to that port viasendToActivePanel(). Also fixes emptyCHAT_RESPONSEpayloads and silent tool execution failures.🐛 PR-5
fix/native-messaging-reconnect— HighSymptom: When the native host crashes, the only recovery is reloading the extension.
Root cause:
onDisconnecthandler only set status toerror— no reconnect was attempted.Fix: Exponential backoff reconnect (5s → 10s → 20s → 30s).
_userDisconnectedflag prevents auto-reconnect when the user intentionally clicks "Disconnect".✨ PR-10
feat/host-auto-discover-cli— HighSymptom: The host fails to start on any platform other than macOS Apple Silicon with a cryptic
ENOENTerror.Root cause: Two hardcoded paths: the shebang
#!/opt/homebrew/bin/nodeandcliPath: '/opt/homebrew/bin/copilot'inCopilotClient. Both only exist on macOS+Homebrew+Apple Silicon.Fix:
#!/usr/bin/env nodefindCopilotCli()that checks common install locations across all platforms (macOS Homebrew, Linux system/user, Windows), then falls back towhich/where🔧 PR-4
refactor/type-safe-native-protocol— MediumProblem:
NativeMessagehad fictional variants (COPILOT_REQUEST,COPILOT_RESPONSE,COPILOT_STREAM) not present in the actual protocol. TypeScript provided zero type safety for the native messaging layer. Also: duplicateHOVER_ELEMENTinContentScriptMessage.Fix: Replace with
HostOutboundMessage(SW→host) andHostInboundMessage(host→SW).NativeMessagekept as@deprecatedalias.🔧 PR-7
fix/type-isstreaming-chatmessage— MediumProblem:
ChatMessagelackedisStreaming, forcing unsafe casts like(msg as ChatMessage & { isStreaming?: boolean }).isStreamingin multiple places.Fix: Add
isStreaming?: booleantoChatMessageintypes.ts, remove all cast workarounds.✨ PR-8
feat/stop-generation— MediumFeature: A red stop button (■) replacing the send button while streaming. Calls
session.abort(), finalizes the partial response without losing already-received text, resets loading state. Textarea disabled during loading to prevent double-sends.✨ PR-3
feat/model-selector— LowFeature:
<select>dropdown in the header showing available models (fetched on connect viaGET_MODELS), allowing mid-session model switching viaSET_MODEL. AddsModelInfotype and full message stack support.🔀 Merge order and conflict map
Files touched by multiple PRs
host.mjsservice-worker.tsApp.tsxmessages.tstypes.tsDependency & merge-order graph
flowchart TD subgraph W1["🌊 Wave 1 — fully independent, merge in any order"] PR9("[PR-9]<br/>fix: tool permission<br/>host.mjs") PR2("[PR-2]<br/>fix: active tab<br/>tab-manager.ts") PR5("[PR-5]<br/>fix: reconnect<br/>native-messaging.ts") PR10("[PR-10]<br/>feat: auto-discover CLI<br/>host.mjs") end subgraph W2["🌊 Wave 2 — type foundations"] PR4("[PR-4]<br/>refactor: protocol types<br/>messages.ts") PR7("[PR-7]<br/>fix: isStreaming type<br/>types.ts · App.tsx") end subgraph W3["🌊 Wave 3 — routing fix"] PR6("[PR-6]<br/>fix: multi-panel routing<br/>service-worker.ts") end subgraph W4["🌊 Wave 4 — needs Wave 2"] PR8("[PR-8]<br/>feat: stop button<br/>host.mjs · service-worker.ts · App.tsx") end subgraph W5["🌊 Wave 5 — needs everything"] PR3("[PR-3]<br/>feat: model selector<br/>7 files") end W1 --> W3 PR7 -->|"needs isStreaming"| PR8 W2 --> PR3 W3 --> PR3 PR8 --> PR3 style PR9 fill:#d73a49,color:#fff style PR2 fill:#d73a49,color:#fff style PR5 fill:#e36209,color:#fff style PR10 fill:#e36209,color:#fff style PR4 fill:#6f42c1,color:#fff style PR7 fill:#6f42c1,color:#fff style PR6 fill:#e36209,color:#fff style PR8 fill:#0366d6,color:#fff style PR3 fill:#0366d6,color:#fffIf applied out of order these will need manual conflict resolution:
host.mjs→ PR-9, PR-10, PR-8, PR-3service-worker.ts→ PR-6, PR-8, PR-3App.tsx→ PR-7, PR-8, PR-3PR-9, PR-2, PR-5 and PR-10 each touch a unique file — they can all be merged simultaneously with zero conflicts.
💬 What I'd suggest
If I had to pick the most impactful subset to merge first:
The refactors (PR-4, PR-7) and features (PR-8, PR-3) are nice-to-have and can be discussed separately.
If any of this is interesting to you, I'm happy to:
Thanks for building this! 🙏