Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>porthole companionNew to Visual Studio Code? Get it now.
porthole companion

porthole companion

Lando-00

|
6 installs
| (0) | Free
Session-aware VS Code companion for Copilot CLI: walkthroughs, annotations, editable change previews, exact-window routing, Problems, and selections sent back.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

porthole companion

The VS Code half of porthole, a GitHub Copilot CLI plugin.

Copilot CLI can already ask VS Code to open a file at a line. It cannot select a range, leave a note on the code it is explaining, walk you through a change step by step, read what your language server is reporting, or take a selection back into the conversation. This extension does those things from inside the window.

This extension does nothing on its own. It is one half of a pair — install the CLI plugin too:

copilot plugin marketplace add Lando-00/porthole
copilot plugin install porthole@porthole-marketplace

Then run /porthole in an interactive Copilot CLI session to check both halves can see each other.

Verified on Windows. The macOS and Linux code paths exist but are not yet tested, so treat them as unproven rather than unsupported — reports welcome.

What you would use it for

"Why does this bug happen?" — instead of a wall of prose describing four files, Copilot builds a walkthrough. The narration sits above the code it explains, and you press Alt+] to follow the path at your own pace.

Reviewing a pull request with several threads. Ask for a walkthrough per thread — the auth change, the error handling, the migration. Each is a separate answer to a separate question, and the sidebar switches between them.

Landing in an unfamiliar module. "Walk me through how a request reaches the database" gives you a guided path through the real code rather than a summary of it.

Picking up where you left off. Walkthroughs are saved and restored in later sessions. A tour written against older code says which steps have gone stale instead of confidently pointing at whatever now sits on those lines.

"What is actually broken?" — Copilot reads your Problems panel, so it works from what your language server reports rather than guessing at compile errors.

Asking about the code in front of you. Select it, press Ctrl+Alt+., and it goes back into the CLI session with its location and any diagnostics attached.

The quickest way to see all of this is /walkthrough in a Copilot CLI session — name something you want explained, and it is built in the window you are looking at. /porthole example demonstrates it on a file it picks for you.

What it adds

Annotations. The agent marks the exact lines it is describing: a coloured underline, a gutter icon, an overview-ruler mark, and a hover carrying its explanation in markdown. Annotations persist until they are cleared or replaced, survive closing and reopening the file, and a status bar item shows the count and jumps between them. They are also published to the Problems panel, so they are navigable with F8 and filterable like any other finding.

Reading the Problems panel. The agent can ask what the language servers and linters are actually reporting, rather than guessing at compile errors from the source. Its own annotations are excluded from that answer.

Walkthrough mode. An ordered, narrated tour of a code path, with the narration and controls in a CodeLens above each step, gutter markers showing progress, and the whole path in the sidebar. Alt+] / Alt+[ to step. Ask for one in conversation, or run /walkthrough <what you want explained> — that command also opens a window if none is running.

A library of walkthroughs. Many tours loaded at once, one active. The active one owns the gutter and the lenses; every loaded one appears in the Problems panel under its own name, so a pull request with three threads reads as three tours rather than one fifty-step list. Tours are saved into the session folder automatically and restored when the window reopens — and, because line numbers rot, each step carries a hash of the code it was written about, so a tour that has gone out of date says so instead of describing whatever now sits there.

Startup restoration uses only the exact session folder present in this workspace. Tours loaded deliberately from older sessions keep their original storage file; moving through one does not adopt it into the current session.

Saved reviews. Save the current findings and load them again later, even from a different session. Findings carry a hash of the code they describe, so a review that has gone stale says so instead of marking innocent lines.

Send to Copilot. Ctrl+Alt+. sends the selection, its location, any diagnostics on those lines and an optional question back into the running CLI session as a prompt.

Range selection. A real multi-line selection, not just a cursor.

Symbol resolution. /goto handleGoto selects the whole function, using the language server when it is warm and a definition scan when it is not.

A sidebar. The current Copilot session, the project and branch, plan.md and checkpoints, the agent's task list grouped by status, and the tour library.

Session identity. A left-side status item shows the session name, repository, branch and connection health. Its stable per-session colour helps distinguish several Porthole windows at a glance; click it to open the sidebar. The health states are Unbound, Connected, Disconnected and Incompatible. An incompatible status names whether the CLI plugin or companion needs updating and which capability is missing.

Session Binding lifecycle. A window stays bound when its CLI endpoint exits, so resuming the exact Copilot session reconnects it. porthole: Clear session binding is an explicit, confirmed action in the Session view and Command Palette. It removes only the session folder from this window; Tours and Saved Reviews remain in the session store.

Editable change previews. Copilot can open proposed replacements as normal VS Code diffs. Edit the PROPOSED side, then explicitly apply or cancel. Porthole refuses stale previews rather than overwriting files changed during review.

Exact-window focus. The CLI can address one windowId and ask that companion to focus its editor or request attention, without opening or modifying a workspace.

Install

From the Marketplace — the Extensions view, or:

code --install-extension Lando-00.porthole-companion
code-insiders --install-extension Lando-00.porthole-companion

Or grab the .vsix from a release:

code-insiders --install-extension porthole-companion-<version>.vsix

To build it yourself:

npm run install-local     # packages the VSIX and installs it into Insiders

A newly installed build is picked up by a new window, not a reload. VS Code resolves extension versions when a window loads, so reloading the extension host respawns the version you already had. If an update appears to have done nothing, this is why.

How the CLI talks to it

Each window publishes a random windowId and the Copilot session folder in its workspace. Normal requests are written to that exact window's owner-only inbox:

1. CLI selects   the window bound to the current Copilot session
2. CLI writes    ~/.copilot/porthole/inbox/<windowId>/<requestId>.json
3. that window   reads the request and does the work
4. companion     writes <tmp>/porthole/ack/<requestId>.json  ->  { ok, ... }
5. CLI polls     for the ack, then reports what actually happened

No socket and no listening port. The inbox is event-driven and scoped to one extension host. URI requests remain supported for manual use and older CLI versions, but a URI can select only Stable versus Insiders — not one particular window when several are open.

Each window also writes ~/.copilot/porthole/companion-<pid>.json while it is running, including windowId, sessionId, sessionFolder, workspace folders, the URI scheme, protocol level and named capabilities. The PID proves liveness; windowId is the address. Compatibility is checked per operation, so independently released plugin and companion versions may work together without having equal product versions.

Two Windows details worth knowing, both learned the hard way:

  • --open-url must be handed the executable (Code - Insiders.exe). The bin\code-insiders.cmd shim blocks and never delivers the URI.
  • The URI authority is the lower-cased extension id: lando-00.porthole-companion.

Routes

Every route acks, including unknown ones.

ping

No payload. Acks with the window's identity — used by /porthole to prove the companion is reachable and to measure the round trip.

{ "ok": true, "windowId": "6f2d9e603f1759a1", "pid": 28556,
  "version": "1.0.0", "uriScheme": "vscode-insiders",
  "sessionId": "eeb847a2-...", "appName": "Visual Studio Code - Insiders",
  "workspaceFolders": ["c:\\Dev\\porthole", "c:\\Users\\me\\.copilot\\session-state\\eeb847a2-..."] }

workspace-add

Takes { folders: ["C:\\repo", "C:\\Users\\me\\.copilot\\session-state\\<id>"] }. Adds only missing absolute folders to this exact window and acknowledges what changed. This is the safe route for an explicit “reuse that window” request; the VS Code CLI's --add flag cannot target a chosen window. The CLI permits this only for an unbound window or the same session; it refuses to mix two Copilot session folders in one companion window.

focus

No payload. Runs only in the addressed companion window, focuses its active editor, and requests attention if the operating system keeps the window in the background. It does not open or alter the workspace.

preview

Takes { title, changes: [{ file, content }] }, where content is the complete proposed file. Opens editable BEFORE/PROPOSED diffs and waits for Apply reviewed changes or Cancel. Before applying, it rejects dirty target editors and verifies that every on-disk file still matches the snapshot shown to the user.

reveal

Opens a file and selects a range. Accepts a payload, or the legacy query form:

vscode-insiders://lando-00.porthole-companion/reveal?file=C%3A%5Ca.js&start=10&end=20
field meaning
file absolute path (required)
start 1-based start line (required)
end 1-based end line (defaults to start)
startCol, endCol 1-based columns; endCol defaults to the end of the line
preserve keep an existing flash instead of replacing it

Acks { ok, file, startLine, endLine, style }.

clear

No payload. Removes the reveal flash.

annotate

{
  "title": "Why /cops opened nothing",   // shown in the status bar tooltip
  "clearExisting": true,                  // default; false adds to the current set
  "focus": 0,                             // which annotation to scroll to
  "annotations": [
    {
      "file": "C:/repo/extension.mjs",
      "startLine": 223,
      "endLine": 240,                     // optional, defaults to startLine
      "startCol": 1, "endCol": null,      // optional
      "severity": "info",                 // info | warn | error | note
      "message": "**This** spawns the editor. Markdown is allowed."
    }
  ]
}

Acks { ok, applied, total, files, rejected? }. An entry without a file is rejected with a reason; an unknown severity falls back to info.

Hovers are rendered with isTrusted = false: the text comes from a model, and a trusted markdown string can embed command links.

annotate-clear

No payload. Removes every annotation.

symbol

{ "query": "handleGoto", "file": "extension.mjs", "preferKind": "function",
  "message": "This is where /goto is handled.", "severity": "info" }

Only query is required. With a message the resolved range is annotated instead of merely revealed, so a jump and its explanation are one action.

Acks { ok, file, startLine, endLine, name, kind, source, candidates? } where source is language-server or text-scan. Equally good matches in different files ack as { ok: false, error: "'x' is ambiguous", candidates: [...] } — jumping to an arbitrary one would be worse than saying so.

diagnostics

{ "scope": "open", "severities": ["error", "warning"], "limit": 100 }

scope is open (the default — visible editors), workspace, or file with a file. Acks { ok, result: { files, counts, annotations, truncated, scanned } }.

porthole's own annotations are excluded from files and reported as annotations, so the agent cannot read its own explanation back as a finding. Entries are sorted worst-first before limit is applied.

open is the useful default: the language server has definitely looked at what is on screen, whereas a cold workspace may never have been indexed.

tour

{ "tourId": "auth-path",              // optional; derived from the title
  "title": "how a request reaches the editor",
  "activate": true,                   // start walking it now, default true
  "replace": true,                    // false refuses an id already loaded
  "steps": [ { "file": "src/tour.js", "startLine": 120, "endLine": 145,
               "stepTitle": "the entry point", "narration": "...",
               "severity": "info" } ] }

1–50 steps per tour, up to 30 tours loaded. file may be absolute or relative to a workspace folder, and must exist — a step that cannot be resolved is reported in result.skipped, never silently dropped. Acks { ok, result: { tourId, replaced, steps, active, library, skipped } }.

tour-list / tour-activate / tour-delete

tour-list takes { includeSteps, repo, limit } and merges what is loaded with what is saved for the exact bound Copilot session. Tours in sibling session folders never enter the ordinary Tour Library. Acks { tours, activeTourId, loaded }.

tour-discover is the separate cross-session lookup. It returns read-only Saved Tour summaries and does not load them. tour-import creates an independent current-session copy with Tour Provenance. Collisions derive a safe ID unless the user explicitly confirms replacement.

tour-archive durably hides one owned Tour from the default library without deleting it. The sidebar offers Archive tour and Restore archived tour; restoring loads the Tour back when its steps resolve. Close remains a temporary Walkthrough pause, while Delete remains confirmed and permanent.

Saved Tour Discovery marks provable historical bug artifacts as LEGACY COPY with timestamp evidence, and malformed evidence as UNCERTAIN. Legacy copies can be imported normally or removed with tour-legacy-cleanup; cleanup is confirmed, exact-file only, and refuses uncertain records.

tour-activate takes { tourId, step }, loading the tour from disk first if it is not in memory. Acks the tour plus a staleness tally when it came off disk.

tour-delete takes { tourId } and removes it from the library and from disk. tour-exit — with an optional { tourId } — only stops walking it; closing is not deleting.

Tours are written to <sessionDir>/porthole/tours/<tourId>.json automatically, debounced, on creation and on every cursor move. Each step stores a hash of the code it describes, so loading can report it as resolved, shifted (found nearby and re-pointed), changed or missing.

review-save / review-list / review-load

review-save takes { slug, title } and writes the current annotations or tour to <sessionDir>/porthole/reviews/<slug>.json.

review-list takes { limit, repo } and returns only the exact bound session's Saved Reviews. review-discover is the separate read-only cross-session search, and review-import creates an independent current-session copy with provenance. review-delete is confirmed and exact-session scoped.

review-load takes { slug } or { file } and acks { ok, result: { review, resolution } }, where resolution counts findings as resolved, shifted, changed or missing. A file must resolve inside a session's reviews folder or it is refused.

Commands

Command Does
porthole: Get started Reopens the getting-started walkthrough
porthole: Clear annotations Removes every annotation
porthole: Next annotation / Previous annotation Steps through the set
porthole: List annotations Quick-pick to jump to one (also the status bar click)
porthole: Next tour step / Previous tour step Alt+] / Alt+[
porthole: List tour steps Quick-pick over the active walkthrough
porthole: Switch tour Quick-pick over the whole library
porthole: Walk this tour / Stop walking this tour Also inline in the sidebar
porthole: Delete tour Removes it from the library and from disk; confirms first
porthole: Exit tour Alt+Escape — stops walking, keeps the tour
porthole: Close all tours Unloads the library; leaves the files alone
porthole: Save review / Load review Persist and restore findings
porthole: Send selection to Copilot Ctrl+Alt+., also on the editor context menu
porthole: Clear highlight Removes the reveal flash
porthole: Refresh session and tasks Reloads the sidebar
porthole: Show reveal URI for the current selection Copies a reveal URI to the clipboard

The three tour keybindings only bind while a tour is running.

Settings

Setting Default Does
porthole.highlight.style both selection, flash, or both
porthole.highlight.flashDurationMs 2500 0 keeps the flash until cleared
porthole.annotations.gutterIcons true Gutter icon beside each annotated range
porthole.annotations.autoRevealFocus true Scroll to the focused annotation on arrival
porthole.gettingStarted.show true Open the getting-started walkthrough the first time this extension runs. It is shown once either way
porthole.problems.publish true Show annotations and tour steps in the Problems panel. Each is marked on its first line only; turn this off for no underline at all
porthole.diagnostics false Log to <tmp>/porthole-companion.log

Turn on porthole.diagnostics when a porthole command appears to do nothing; it applies immediately, without a reload.

The sidebar

Session — the session id, the project and its branch, plan.md, and the checkpoint count. Items open what they name.

Tasks — the agent's todo list grouped by status (in progress, pending, blocked, done), with the description as a tooltip and depends on: children for blocked work.

Tours — every walkthrough loaded in this window. The active one is expanded and shows where you are in it; the rest are collapsed, and clicking one starts walking it. A tour whose code has moved on says how many steps are stale, and those steps carry a warning icon. A single tour skips the folder and shows its steps directly. Empty until the agent creates one.

Both read the session folder, found either from a workspace folder under ~/.copilot/session-state (what /cops produces) or from a session holding a live inuse.<pid>.lock. session.db is read read-only via node:sqlite, falling back to whatever node is on your PATH.

Refreshing happens on demand, when a view becomes visible, and when the window regains focus. There is no file watcher and no polling.

Licence

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft