Skip to content

[RFC] Bug fixes + features — WSL support, multi-panel routing, model selector, stop button #1

@svg153

Description

@svg153

[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.

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:

svg153/github-copilot-browser — PR #1: feat: WSL native messaging, tool execution fix, model selector, stop button, session routing

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 main on my fork, so you can cherry-pick exactly what you want:

# PR Type Priority Files changed
1 fix: approve all tool permission requests automatically 🐛 Bug fix Critical host.mjs
2 fix: use lastFocusedWindow for active tab detection in side panel 🐛 Bug fix Critical tab-manager.ts
3 fix: route chat responses only to the panel that initiated the request 🐛 Bug fix High service-worker.ts
4 fix: auto-reconnect to native host with exponential backoff 🐛 Bug fix High native-messaging.ts
5 feat: auto-discover Copilot CLI binary instead of hardcoding macOS path ✨ Feature High host.mjs
6 refactor: replace wrong NativeMessage type with correct host protocol types 🔧 Refactor Medium messages.ts
7 fix: add isStreaming to ChatMessage type, remove unsafe casts 🔧 Refactor Medium types.ts, App.tsx, MessageList.tsx
8 feat: stop/abort button to interrupt generation mid-stream ✨ Feature Medium ChatInput.tsx, App.tsx, service-worker.ts, host.mjs
9 feat: model selector — list and switch Copilot models from the header ✨ Feature Low types.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-allCritical

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.

// Before
session = await client.createSession({ tools: browserTools, ... });

// After
import { approveAll } from '@github/copilot-sdk';
session = await client.createSession({ tools: browserTools, onPermissionRequest: approveAll, ... });

🐛 PR-2 fix/active-tab-side-panelCritical

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.


🐛 PR-6 fix/multi-panel-message-routing — High

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.


🐛 PR-5 fix/native-messaging-reconnect — High

Symptom: When the native host crashes, the only recovery is reloading the extension.

Root cause: onDisconnect handler only set status to error — no reconnect was attempted.

Fix: Exponential backoff reconnect (5s → 10s → 20s → 30s). _userDisconnected flag prevents auto-reconnect when the user intentionally clicks "Disconnect".


PR-10 feat/host-auto-discover-cli — High

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

🔧 PR-4 refactor/type-safe-native-protocol — Medium

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.


🔧 PR-7 fix/type-isstreaming-chatmessage — Medium

Problem: ChatMessage lacked isStreaming, forcing unsafe casts like (msg as ChatMessage & { isStreaming?: boolean }).isStreaming in multiple places.

Fix: Add isStreaming?: boolean to ChatMessage in types.ts, remove all cast workarounds.


PR-8 feat/stop-generation — Medium

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.


PR-3 feat/model-selector — Low

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.


🔀 Merge order and conflict map

Files touched by multiple PRs

File PRs that touch it
host.mjs PR-9 · PR-10 · PR-8 · PR-3
service-worker.ts PR-6 · PR-8 · PR-3
App.tsx PR-7 · PR-8 · PR-3
messages.ts PR-4 · PR-3
types.ts PR-7 · PR-3

Dependency & 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:#fff
Loading

Legend: 🔴 Critical bug fix · 🟠 High priority · 🟣 Refactor / type fix · 🔵 Feature

⚠️ PRs with file conflicts (same file, different content)

If applied out of order these will need manual conflict resolution:

PR-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:

  1. PR-9 — Without this, no browser tool works at all. One-line fix.
  2. PR-2 — Without this, all content-script tools fail when the panel has focus.
  3. PR-10 — Without this, the host only starts on one specific platform.
  4. PR-5 — The extension recovers automatically from host crashes.
  5. PR-6 — Multi-window users see wrong responses without this.

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:

  • Open PRs directly on this repo following your contribution guidelines
  • Adjust the scope or implementation of any change
  • Add tests if you have a test setup I should follow
  • Break things down further or combine them differently

Thanks for building this! 🙏

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions