Open any Paseo workspace in vscode.dev — real VS Code in a browser, attached to the actual worktree on the machine running the Paseo daemon.
Paseo already hands a workspace to a local editor. This adds the web one, so a worktree is reachable from a browser tab, a second machine, or a phone — including when the daemon is a headless box you have no editor installed on.
VS Code Cloud in the sidebar — every workspace on the host in one list, grouped by project, each with its links. Machine setup happens here once.
VS Code Web as a panel on each workspace, and on each agent — where it can also open the editor inside Paseo. Both are reachable from ⌘K.
Both offer up to two links:
| Link | Reaches | Terminal | Needs |
|---|---|---|---|
vscode.dev/tunnel/<machine>/<path> |
the live worktree, uncommitted changes and all | yes | a VS Code tunnel, set up from the panel |
vscode.dev/github/<owner>/<repo>/tree/<branch> |
the pushed branch on GitHub | no | a GitHub remote and an upstream branch |
The tunnel link is the interesting one: the same files the agent is editing, with a working terminal, from any browser.
git clone https://github.com/itsjustanks/paseo-plugin-vscode-web.git
cd paseo-plugin-vscode-web
npm install
paseo plugin install "$PWD"Needs Paseo 0.5.0-beta.1 or newer (where local plugins landed) and pluginsEnabled: true in the
daemon's config.json.
Open VS Code Cloud from the sidebar. Three steps, each ticking off as it completes:
- VS Code CLI — found automatically if it is already on the machine, including inside an
installed VS Code, where it usually is not on
PATH. Otherwise press Download CLI: the plugin fetches Microsoft's standalone, editor-free CLI for the detected platform into$PASEO_HOME/plugin-data/vscode-web/cli. Nothing is installed system-wide and no package manager is involved, which is what makes a headless Linux daemon behave like a Mac. SetPASEO_VSCODE_CLIto point at a specific binary instead. - Connect this machine — one GitHub device login. The code appears in the panel and it polls until GitHub accepts it.
- Start tunnel — installs the tunnel as a background service, so it survives a reboot.
Every workspace then shows its links. Stop tunnel uninstalls the service and kills the tunnel.
Supported download targets: darwin and win32 on x64/arm64, linux on x64/arm64/armhf, and
alpine on x64/arm64 (musl).
Open launches the daemon machine's default browser.
To keep the editor inside Paseo, copy the link into a Paseo browser tab. vscode.dev renders
there in full, because Paseo's browser tabs are Electron <webview> guests and a guest is its own
frame-tree root, so the content-security-policy: frame-ancestors 'none' that vscode.dev serves
never applies to it.
On an agent tab the panel offers Open as a Paseo tab, which does land the editor inside
Paseo automatically. It works by asking the agent to call its own browser_new_tab tool — that
tool's whole job is "create a new Paseo browser tab in this agent's workspace at a URL".
The trade is honest: it costs the agent one turn, it only exists where an agent does, and it needs
daemon.browserTools.enabled: true in the daemon's config.json (the panel says so if it fails).
If you would rather not spend a turn, the copy-and-shortcut route is right there beside it.
Paseo can open a browser tab at a URL from three places — the renderer's
createWorkspaceBrowser({ initialUrl }), the daemon's browser-automation channel
(new_tab/navigate), and the agent tools above. None is reachable from plugin code:
- the plugin SDK covers agents, workspaces, providers and config, with no browser namespace;
- command callbacks get only
openSurface/openPanel, limited to the plugin's own registrations; paseo://deep links route to agents, workspaces, settings, projects and plugin surfaces — but there is no browser route;- the daemon's browser broker is wired solely to agent tools;
- plugin surfaces are React Native with no WebView, so a panel cannot render a page itself.
Hence: an agent can, a plugin cannot, and the agent hand-off is the bridge.
- A tunnel is bound to your GitHub account. It is not a public URL — opening it prompts for the same sign-in. It does expose the paths you point it at, with a terminal, so treat it like SSH access to that machine.
- Plugin backends are unsandboxed by design. This one only opens
httpslinks onvscode.dev,insiders.vscode.dev,github.dev, andgithub.com, and refuses everything else, so a link cannot become an arbitrary command. - Workspace directories are resolved and checked to be real directories before any
gitcall. - No token is read or stored by the plugin; the VS Code CLI owns the tunnel credential under
~/.vscode/cli. Nothing is written to your repositories.
- The GitHub link shows the pushed commit. A branch with no upstream gets no link, and the view says so rather than offering a 404.
- The daemon machine must be awake for a tunnel link to resolve.
- The tunnel machine name comes from the VS Code CLI (
~/.vscode/cli/code_tunnel.json), falling back to the hostname. Rename withcode tunnel rename <name>. - Sidebar ordering is Paseo's; a plugin contributes a title and a Lucide icon, not a position.
- Branch state is cached for 45 seconds, so a fresh push can take that long to appear.
code-server never touches vscode.dev — it is a separate self-hosted server on 127.0.0.1:8080
with its own ?folder= URL. On the machine you are sitting at, that is a fine alternative. On a
remote or headless daemon it is worse: you would have to reach that port over SSH, a VPN, or a
reverse proxy, whereas a VS Code tunnel dials out and vscode.dev reaches it from anywhere with
nothing opened. So the tunnel wins exactly where a web editor is most useful.
npm run typecheck
paseo plugin reload vscode-web
paseo plugin logs vscode-web*.client.tsx runs in the app (React Native), *.server.ts in a Node subprocess beside the
daemon, *.shared.ts holds the Zod contracts between them. Paseo rejects cross-imports.
Machine-level state (CLI, account, tunnel) and workspace-level state (links) are deliberately
separate contracts. That is what lets the sidebar show setup once and list every workspace from a
single vscode.workspaces call resolved entirely on the daemon.
Two things worth knowing if you extend it. Surfaces do not share a React Query cache, so a
panel cannot rely on another surface invalidating it — each polls to stay honest. And the
workspace payload on the wire carries workspaceDirectory / projectRootPath, not the
directory field that the client-side snapshot exposes.
MIT