Show navigation
Host
agent-console-server is the half of the agent console that runs where the code is. It holds everything sensitive: the Claude Code login, the repositories and the sessions. The page holds only the host's address, its own credential and the host's public key, in the browser's own storage. No session, key or repository ever reaches Esposter's servers. The app serves the page and nothing else.
How it works
flowchart TD
Start[pnpm dlx agent-console-server] --> Key[Read the host key from the home directory, or make one]
Key --> Print[Print a one-time pairing link]
Print --> Open[The page opens the link and asks the reader to Connect]
Open --> Pair[The page pairs with the link's code and keeps a credential]
Pair --> Replay
Upgrade{The host signs the page's challenge, and the credential is known?}
Upgrade -->|no| Refuse[The socket closes]
Upgrade -->|yes| Replay[Every open session's log replayed, then the session list]
Replay --> Live[Live events broadcast to every page; commands answered to the page that sent them]
Live -->|socket drops| Retry[The page retries, backing off up to thirty seconds, until the host answers]
Retry --> Upgrade
Live -->|Ctrl+C or the window closed| Stopping[HostStopping to every page, then every session closed]
Stopping --> Stopped[The page shows the host stopped and waits for Reconnect]
Stopped -->|Reconnect| Upgrade
- One command, one socket. The host is a plain HTTP listener with a WebSocket upgrade. It listens on
127.0.0.1at a fixed default port, so the URL it prints is predictable.--hostname 0.0.0.0makes it reachable from another machine, over whatever network already reaches that machine. Esposter runs no relay. The deployed site can use that only through a tunnel: a page onhttpsmay open a plainws://socket to the loopback and nowhere else. The loopback is a potentially trustworthy origin, and the browser blocks any other address as mixed content. So from the deployed site, a host on another machine is reached through something that makes it loopback again, such as an SSH port forward that brings the host's port to this machine's loopback. The host keeps its default loopback binding there, and the page pairs with the forwarded port through the printed link. The pairing code, the credential and every message cross that path, so the tunnel is an encrypted one, never plainws://across the network. A page on the local dev server is plainhttpand can reach the other machine directly. - Pairing. Each browser pairs with a credential of its own, and every connect has the host prove itself before the page sends it (device pairing). The host keeps the paired devices and its key in
~/.agent-console-server, readable by the person alone, so a page paired once stays paired across restarts. The host prints a link to the app's/genshinwith its address and a one-time code in the URL fragment. A fragment never reaches a server, and the page reads it once and clears it. The link asks rather than pairs: a link is anyone's to craft, and one that paired on its own would send everything the reader types to whichever host its author runs, so the page asks the reader to Connect, warns in plain words when the link points at another computer, and a page already paired keeps its host.--originpoints the link at a local dev server instead of the deployed site, and is the one origin whose pages may connect. The page offers no field to paste an address into; the first screen is the steps to download and open the host installer, and Connect. - The private-network preflight. A page on
httpsreaching a loopback address is a local network request. Current Chrome gates it on its Local Network Access permission, a prompt the person answers once per site, rather than on the private-network preflight it replaced. The host still answers everyOPTIONSwithAccess-Control-Allow-Private-Networkfor a browser that sends the preflight. A page on the local dev server is loopback to loopback and needs neither. - The replayed log. The host keeps every open session's events from the moment it opened, and drops an event already logged under its id. A page that connects or reconnects receives the whole log first. A session reopened under the same id, which is what a rewind does, has its log reset, and every page is told to drop what it held.
- Commands. Each command carries an id the page chose. A command that opens a session is answered with the session it opened, so the page that asked moves to it. A command that fails is answered with why, and a message that is not a command at all is answered under an empty id.
Key files
| File | Role |
|---|---|
packages/agent-console-server/src/services/cli/commands/agentConsoleServerCommand.ts | The command and its subcommands, each with citty's help |
packages/agent-console-server/src/services/cli/serveHost.ts | The host key, the printed link, closing on Ctrl+C |
packages/agent-console-server/src/services/server/createAgentConsoleServer.ts | The listener, the handshake, the log replay and the command replies |
packages/agent-console-server/src/services/server/answerHttpRequest.ts | The preflight answer, and a refusal for every other plain request |
packages/agent-console-server/src/services/server/createEventLog.ts | Each open session's log, one event per id |
apps/web/app/store/agentConsole/connection.ts | The page's side: pairing, the socket, the backoff, routing what arrives |
Notes
- The page trusts only the host it paired with. Loopback is shared by every user and program on the computer, so a process can take the port first — on a shared machine, or while the host is closed. The page sends such a process nothing, since it cannot sign the page's challenge with the host's key (device pairing).
- Nothing looks for a host before pairing. The page once sent a plain request to the loopback port to say whether a host was running, and the host answered any origin. That let every site the reader visited learn the host was there, and had the deployed site reach into the reader's machine before being asked. The page now asks the loopback for nothing until the reader presses Connect or opens the printed link, and the host refuses every plain request other than the preflight with no CORS headers.
- Stopping the host is announced. Ctrl+C, or closing the window it runs in, which Windows reports as
SIGHUP, sends every connected pageHostStoppingbefore the host closes every session's Claude Code process, and every session window with it, and exits, rather than leaving them orphaned. The page shows the host stopped and its sessions closed, and waits for Reconnect rather than retrying; only a host that goes without a word is shown as not answering and retried. A second close waits on the first.
Scroll to top