Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 31 additions & 19 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ Copilot CLI ──(Streamable HTTP over named pipe)──▶ CopilotCliIde.Serve
CopilotCliIde (VS extension, net472)
```

- **CopilotCliIde** (`net472`) — The VS extension package. Loads when a solution opens, manages the connection lifecycle (pipes, server process, lock file), subscribes to DTE events, exposes VS services via `VsServiceRpc`, and hosts an embedded terminal (WebView2 + ConPTY) for running Copilot CLI inside VS.
- **CopilotCliIde** (`net472`) — The VS extension package. Loads when a solution opens, manages the connection lifecycle (pipes, server process, lock file), subscribes to DTE events, exposes VS services via `VsServiceRpc`, and hosts an embedded terminal (Microsoft.Terminal.Wpf + ConPTY) for running Copilot CLI inside VS.
- **CopilotCliIde.Server** (`net10.0`) — ASP.NET Core (Kestrel) process hosting the MCP server on a Windows named pipe. Uses `ModelContextProtocol.AspNetCore` for the Streamable HTTP transport — Kestrel handles HTTP/1.1 framing, SSE streaming, and session management. `AspNetMcpPipeServer` is the server entry point; `TrackingSseEventStreamStore` manages SSE stream lifecycle. Contains 7 MCP tools in the `Tools/` folder.
- **CopilotCliIde.Shared** (`netstandard2.0`) — Shared RPC contracts (`IVsServiceRpc`, `IMcpServerCallbacks`) and DTOs used by both the extension and the server.

### Connection Lifecycle

The connection is tied to the VS solution lifecycle (matching VS Code's close-folder behavior):
- **Solution opens** → `StartConnectionAsync()` creates new pipes, launches the MCP server process, and writes a lock file to `~/.copilot/ide/`.
- **Solution closes** → `StopConnection()` removes the lock file, kills the server process, and disposes pipes. Copilot CLI disconnects.
- **Solution closes** → `StopConnection()` removes the lock file, kills the server process, and disposes pipes. Copilot CLI disconnects. The embedded terminal is restarted in the current directory (not stopped).
- **Solution switches** → `StopConnection()` then `StartConnectionAsync()` — a fresh connection for the new workspace.

### Discovery
Expand Down Expand Up @@ -103,16 +103,16 @@ Tool names and schemas must match VS Code's Copilot Chat extension exactly (`get

## Embedded Terminal Subsystem

The extension hosts Copilot CLI in a dockable tool window (**Tools → Copilot CLI (Embedded Terminal)**) using Windows ConPTY + WebView2 + xterm.js. This is a UI-only feature — it does not interact with the MCP server or RPC layer.
The extension hosts Copilot CLI in a dockable tool window (**Tools → Copilot CLI (Embedded Terminal)**) using Windows ConPTY + `Microsoft.Terminal.Wpf.TerminalControl` — the same native terminal control used by Windows Terminal and VS's own embedded terminal. This is a UI-only feature — it does not interact with the MCP server or RPC layer.

### Architecture

```
TerminalToolWindowControl (WPF) ──(WebView2 postMessage)──▶ xterm.js (terminal.html)
│ attach/detach resize / input events
TerminalSessionService (singleton) ───────────────────────────────┘
TerminalToolWindowControl (WPF, implements ITerminalConnection)
│ attach/detach │ TerminalOutput event (string)
▼ │
TerminalSessionService (singleton) ─── OutputReceived ──▶ TerminalControl
TerminalProcess ──(pipes)──▶ ConPTY pseudo-console ──▶ cmd.exe /c copilot
Expand All @@ -123,33 +123,45 @@ TerminalProcess ──(pipes)──▶ ConPTY pseudo-console ──▶ cmd.exe /
- **`ConPty.cs`** — P/Invoke wrapper for `CreatePseudoConsole`, `ResizePseudoConsole`, `ClosePseudoConsole` and related Win32 APIs. Requires Windows 10 1809+. The `ConPty.Session` class holds all native handles and disposes them in the correct order (close pseudo-console first to signal EOF, then pipes, then process/thread handles).
- **`TerminalProcess.cs`** — Manages the `ConPty.Session` lifecycle. Spawns `cmd.exe /c copilot` in the solution directory. Reads output on a dedicated background thread with 16ms batching (60fps) via `Timer` + `StringBuilder`. Fires `OutputReceived` and `ProcessExited` events.
- **`TerminalSessionService.cs`** — Package-level singleton (created in `InitializeAsync`, stored in `VsServices.Instance`). The tool window control attaches/detaches from this service — the process survives window hide/show cycles. Supports `StartSession`, `StopSession`, `RestartSession`, `WriteInput`, `Resize`.
- **`TerminalToolWindow.cs`** — `ToolWindowPane` shell. Overrides `PreProcessMessage` to prevent VS from intercepting arrow keys, Tab, Escape, Enter, etc. — lets them reach WebView2/xterm.js.
- **`TerminalToolWindowControl.cs`** — WPF `UserControl` hosting WebView2. Initializes lazily via `Dispatcher.BeginInvoke(ApplicationIdle)` to avoid blocking VS startup. Maps `Resources/Terminal/` to a virtual hostname (`copilot-cli.local`) for WebView2 content. Communicates with xterm.js via JSON `postMessage`/`WebMessageReceived`.
- **`Resources/Terminal/`** — `terminal.html`, `terminal-app.js`, and bundled xterm.js/FitAddon libraries. The JS sends `{ type: "input", data }` and `{ type: "resize", cols, rows }` messages to C#; C# posts `{ type: "output", data }` back.
- **`TerminalToolWindow.cs`** — `ToolWindowPane` shell. Overrides `PreProcessMessage` to prevent VS from intercepting arrow keys, Tab, Escape, Enter, etc. — lets them reach the native `TerminalControl`.
- **`TerminalToolWindowControl.cs`** — WPF `UserControl` implementing `ITerminalConnection`. Hosts `Microsoft.Terminal.Wpf.TerminalControl` directly — zero marshaling overhead. `WriteInput` → session service, `Resize` → session start or resize, `TerminalOutput` event ← output received.
- **`TerminalThemer.cs`** — Reads VS color theme and maps it to a `TerminalTheme` struct for the native control.
- **`TerminalSettings.cs`** — Reads terminal font family and size from `WritableSettingsStore` (path `CopilotCliIde\Terminal`). Provides static `FontFamily` and `FontSize` accessors with defaults (`Cascadia Code`, 12pt).
- **`TerminalSettingsProvider.cs`** — Implements `IExternalSettingsProvider` for VS Unified Settings. Dynamically enumerates installed monospace fonts via GDI+ `InstalledFontCollection` + character-width measurement. The font dropdown uses `allowsFreeformInput` so users can type arbitrary names.
- **`registration.json`** — VS Unified Settings manifest declaring the `copilotCliIde.terminal` category with `fontFamily` (enum + freeform) and `fontSize` (integer) properties. Uses `"type": "external"` with a callback to `TerminalSettingsProvider`.

### Unified Settings

The extension exposes terminal font configuration through VS's Unified Settings API (**Settings → Copilot CLI IDE Bridge → Terminal**):

- `[ProvideSettingsManifest]` on the package registers `registration.json` as a settings manifest.
- `[ProvideService(typeof(TerminalSettingsProvider))]` proffers the external settings provider.
- `registration.json` declares properties with `"type": "external"` and a callback containing the package and service GUIDs.
- `GetEnumChoicesAsync` **must** use `await Task.Yield()` — VS silently drops enum choices from synchronous Task returns.
- Settings are persisted in `WritableSettingsStore` under `CopilotCliIde\Terminal` and read by `TerminalSettings` at theme application time.

### Lifecycle

- **Package init** → `TerminalSessionService` created and stored in `VsServices.Instance.TerminalSession`.
- **Tool window opened** → `TerminalToolWindowControl` creates WebView2 lazily, attaches to session service. Process is **not** started until xterm.js sends the first `resize` message (so ConPTY gets correct initial dimensions).
- **Tool window opened** → `TerminalToolWindowControl` creates `TerminalControl`, sets `Connection = this`. Process is **not** started until the control fires `Resize` (so ConPTY gets correct initial dimensions).
- **Solution opens** → `_terminalSession.RestartSession(workspaceFolder)` re-launches the CLI in the new solution directory.
- **Solution closes** → `_terminalSession.StopSession()` kills the CLI process.
- **Solution closes** → `_terminalSession.RestartSession(GetWorkspaceFolder())` restarts the CLI in the current directory (keeps terminal alive across solution switches).
- **Process exits** → User sees `[Process exited. Press Enter to restart.]`; pressing Enter calls `RestartSession()`.
- **Package dispose** → `_terminalSession.Dispose()` tears down everything.

### Threading

- WebView2 init and DOM interaction happen on the UI thread (via `JoinableTaskFactory`).
- `TerminalProcess.ReadLoop` runs on a dedicated `IsBackground = true` thread.
- Output is dispatched to WebView2 via `Dispatcher.BeginInvoke` (lighter than JTF for fire-and-forget).
- `OnWebMessageReceived` runs on UI thread — accesses `_sessionService` directly.
- Output is delivered to `TerminalControl` via the `TerminalOutput` event (native control handles its own threading).
- Session start is dispatched via `Dispatcher.BeginInvoke` from the `Resize` callback to access DTE on the UI thread.

### Independence from MCP/Connection System

The terminal subsystem is **completely independent** of the MCP server, RPC pipes, and lock file discovery. It is a direct ConPTY → WebView2 bridge. The only shared touchpoints are:
The terminal subsystem is **completely independent** of the MCP server, RPC pipes, and lock file discovery. It is a direct ConPTY → native terminal bridge. The only shared touchpoints are:
- `VsServices.Instance` (service locator for the singleton)
- `CopilotCliIdePackage` (lifecycle management — solution open/close hooks)
- `GetWorkspaceFolder()` (shared utility for solution directory)

### WebView2 Dependency
### Terminal.Wpf Dependency

The extension depends on `Microsoft.Web.WebView2` NuGet package. WebView2 runtime is pre-installed on Windows 10 1809+ and all Windows 11 machines. The user data folder is stored at `%LOCALAPPDATA%\CopilotCliIde\webview2`.
The extension references `Microsoft.Terminal.Wpf.dll` which ships with Visual Studio — no additional runtime dependency is needed. The DLL location varies by VS channel: `CommonExtensions\Microsoft\Terminal\` (Community/Insiders) or `CommonExtensions\Microsoft\Terminal\Terminal.Wpf\` (Canary). The csproj probes both paths at build time, and the `AssemblyResolve` handler in `CopilotCliIdePackage` does the same at runtime. The reference uses `Private=false` so the DLL is not copied to output (it's already loaded in the VS AppDomain).
6 changes: 3 additions & 3 deletions .squad/agents/hicks/charter.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- **Name:** Hicks
- **Role:** Extension Dev
- **Expertise:** Visual Studio extensibility (VSSDK), DTE/COM interop, IVsMonitorSelection, IWpfTextView, UI threading, named pipes, ConPTY, WebView2
- **Expertise:** Visual Studio extensibility (VSSDK), DTE/COM interop, IVsMonitorSelection, IWpfTextView, UI threading, named pipes, ConPTY, Microsoft.Terminal.Wpf
- **Style:** Thorough, methodical. Tests threading assumptions before writing code. Documents gotchas.

## What I Own
Expand All @@ -16,7 +16,7 @@
- Selection tracking (IVsMonitorSelection, IWpfTextView, ITextDocument)
- VS service integration and DTE event handling
- InfoBar UI for diff accept/reject flow
- Embedded terminal subsystem (ConPTY, WebView2/xterm.js, TerminalProcess, TerminalSessionService, TerminalToolWindow)
- Embedded terminal subsystem (ConPTY, Microsoft.Terminal.Wpf, TerminalProcess, TerminalSessionService, TerminalToolWindow)

## How I Work

Expand All @@ -28,7 +28,7 @@

## Boundaries

**I handle:** VS extension code, threading, DTE events, selection tracking, pipe client, lock files, InfoBar UI, embedded terminal (ConPTY/WebView2/xterm.js).
**I handle:** VS extension code, threading, DTE events, selection tracking, pipe client, lock files, InfoBar UI, embedded terminal (ConPTY/Microsoft.Terminal.Wpf).

**I don't handle:** MCP server code or tool implementations — that's Bishop. Tests — that's Hudson. Architecture decisions — that's Ripley.

Expand Down
40 changes: 40 additions & 0 deletions .squad/agents/hicks/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,43 @@ Parallel parenthetical naming so both group together and the distinction is obvi

**Build:** VSIX build clean (MSBuild, 0 errors). `addon-webgl.js` auto-included via `Resources\Terminal\**\*.*` glob in CSPROJ.

### 2026-07-21 — Replace WebView2+xterm.js with native Microsoft.Terminal.Wpf

Completed full migration from WebView2/Chromium/xterm.js terminal rendering to VS's built-in `Microsoft.Terminal.Wpf.TerminalControl` — the same native control used by Windows Terminal and VS's own embedded terminal.

**Key implementation decisions:**

1. **VS-deployed assembly reference (not NuGet):** `Microsoft.Terminal.Wpf.dll` and `Microsoft.Terminal.Control.dll` ship with VS at `CommonExtensions\Microsoft\Terminal\`. Used `<Reference Include="Microsoft.Terminal.Wpf">` with `<HintPath>$(DevEnvDir)CommonExtensions\Microsoft\Terminal\...</HintPath>` and `<Private>false</Private>`. This skips NuGet entirely — no CI package stability risk, no native DLL bundling in VSIX, no assembly resolution gymnastics. The DLL resolves from VS's AppDomain at runtime.

2. **ITerminalConnection bridge:** `TerminalToolWindowControl` implements `ITerminalConnection` directly (matching VS's own `TerminalControl.xaml.cs` pattern). Interface surface: `Start()` (no-op, deferred to Resize), `WriteInput(string)` → `TerminalSessionService.WriteInput()`, `Resize(uint, uint)` → session start or resize, `Close()` (no-op), `TerminalOutput` event ← `OnOutputReceived`. Zero marshaling overhead — direct method calls replace JSON-over-WebView2-messaging.

3. **Theme detection without internal APIs:** VS's `TerminalThemer.cs` uses `IVsColorThemeService` (internal PIA). We can't access that from a third-party extension. Instead, detect dark/light theme by checking luminance of `EnvironmentColors.ToolWindowBackgroundColorKey` using ITU-R BT.601 luma formula. Color tables copied exactly from VS's own `TerminalThemer.cs`.

4. **What was deleted:**
- `Resources/Terminal/` — terminal.html, terminal-app.js, lib/{xterm.js, xterm.css, addon-fit.js, addon-webgl.js}
- `Microsoft.Web.WebView2` NuGet from both `Directory.Packages.props` and `CopilotCliIde.csproj`
- xterm.js npm dependencies from `package.json`
- All WebView2 init code, JSON messaging, deferred loading, focus recovery hacks

5. **What was preserved:**
- `TerminalProcess.cs`, `TerminalSessionService.cs`, `ConPty.cs` — unchanged
- `PreProcessMessage` in `TerminalToolWindow` — still needed for key routing
- Deferred session start on first Resize — same pattern, simpler path

**API surface (Microsoft.Terminal.Wpf v1.22.0.0):**
- `TerminalControl` — WPF UserControl wrapping native `TerminalContainer` (HwndHost)
- `ITerminalConnection` — Start, WriteInput, Resize, Close, TerminalOutput event
- `TerminalTheme` — ColorTable[16], DefaultBackground/Foreground/SelectionBackground, CursorStyle
- `SetTheme(TerminalTheme, string fontFamily, short fontSize)` — 4th param `Color externalBackground` is optional
- Assembly: `PublicKeyToken=f300afd708cefcd3`, requires `System.Xaml` reference in consuming project

**Build:** MSBuild 0 errors, 0 warnings. All 284 server tests pass. Formatter clean.

### 2026-07-24 — VsInstallRoot for Terminal.Wpf HintPath

Replaced `$(DevEnvDir)` with `$(VsInstallRoot)\Common7\IDE\` in the Microsoft.Terminal.Wpf assembly HintPath. `$(VsInstallRoot)` is set by the VSSDK NuGet targets (Microsoft.VSSDK.BuildTools) which use vswhere internally — works both inside VS IDE and from command-line MSBuild without manual overrides.

This eliminated the `Find VS install path` CI step and `/p:DevEnvDir=...` override from both ci.yml and release.yml. Simpler, less fragile.

**Key insight:** `$(DevEnvDir)` includes the trailing `Common7\IDE\` path segment (e.g., `C:\VS\Common7\IDE\`), while `$(VsInstallRoot)` is just the root (e.g., `C:\VS`). So the HintPath needed `\Common7\IDE\` inserted.

Loading