diff --git a/public/blog/banners/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.svg b/public/blog/banners/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.svg new file mode 100644 index 0000000..b28d9df --- /dev/null +++ b/public/blog/banners/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.svg @@ -0,0 +1,8 @@ + diff --git a/src/data/blogPosts.json b/src/data/blogPosts.json index c14bd71..ddbbbd7 100644 --- a/src/data/blogPosts.json +++ b/src/data/blogPosts.json @@ -1615,5 +1615,15 @@ "tags": ["shell-online", "terminal-sharing", "coding-agents", "sandbox", "end-to-end-encryption"], "banner": "banners/share-a-terminal-session-in-the-browser-without-handing-over-the-filesystem-shel.svg", "iso_date": "2026-09-13" + }, + { + "slug": "connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin", + "title": "Connect an AI Agent to a Live Terminal Without Giving It the Machine: shell.online v0.15 Agent Handoffs", + "description": "How to connect an AI agent to a live terminal with shell.online v0.15: a revocable invitation, explicit completion evidence, and a process that stays yours.", + "date": "Sep 14", + "category": "Engineering", + "tags": ["shell-online", "ai-agents", "terminal", "refstream", "openai"], + "banner": "banners/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.svg", + "iso_date": "2026-09-14" } ] diff --git a/src/pages/blog/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.astro b/src/pages/blog/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.astro new file mode 100644 index 0000000..d9893e9 --- /dev/null +++ b/src/pages/blog/connect-an-ai-agent-to-a-live-terminal-without-giving-it-the-machine-shell-onlin.astro @@ -0,0 +1,72 @@ +--- +import BlogLayout from '../../layouts/BlogLayout.astro'; + +const bodyContent = `
OpenAI opened its Agents API to public beta on September 10. The pitch is a managed Codex harness: you supply files, install packages, and add skills, in the announcement's words, "while we provision and manage the sandbox", with partner sandboxes from Cloudflare, E2B, Modal, and others if you would rather pick the container yourself. That is one answer to the question of where an agent should do its work. Today's shell.online release, v0.15.0, is built for the opposite arrangement. The terminal stays on your machine, with your files and your permissions, and you connect an AI agent to a live terminal through a single-use invitation that you can revoke at any moment without stopping the process.
+ +shell.online has always been one command that prints a URL and a password for a running process. The browser view of that process defaults to xterm.js. Since v0.14.0 there has been an optional second renderer, Refstream, which is still labelled an unstable alpha and which changes only the browser view, never the PTY, the relay permissions, or the encryption. Refstream is where the Connect agent button lives.
+The v0.15.0 changelog lists two additions. First, reusable agent connections: a connected agent keeps its handle for follow-up requests, and task IDs, progress, and collected answers survive panel changes and reconnects. The README now says the agent connects once and follows up without pairing again. Second, tab-local session recovery for page reloads. The snapshot expires after four hours, disappears when the tab closes, and never goes to the relay. Recovery also still requires the local process to be alive, because a snapshot restores parser state and task records, not a shell.
+v0.15.1, released the same morning, bumps the vendored renderer to Refstream v0.1.0-alpha.5 and fixes a real bug in that recovery path: a restored snapshot could answer historical terminal capability queries into the live process, which could corrupt tmux input. The README now states the rule directly: a restored browser snapshot cannot answer historical terminal queries into the live process.
+Start with any process you would normally share. A coding agent is the obvious case, but a dev server or a plain shell works the same way.
+shell claude
+# or
+shell npm run dev
+The CLI prints the share URL, the password, and a QR code. Open the link in a browser, open Terminal controls, and switch the renderer to Refstream (unstable alpha). The choice is saved in that browser only. Then choose Connect agent and pick one of two grants, both documented in the CLI reference: Read terminal, which lets the agent read current output, search retained rows, inspect command boundaries, and wait for changes; or Read and control, which additionally lets it type, execute a command against a recent terminal sequence, and send key combinations such as Ctrl-C.
+What you copy is not a password. It is a private invitation plus a standalone Node.js 22 or newer connector URL with a pinned SHA-256 checksum. The invitation is single-use and expires after five minutes. The agent verifies that file against the checksum, then you pass the invitation through its ordinary command tool, and the agent handoff protocol is explicit that it must arrive on stdin or through the connector's hidden prompt, never in command arguments, URLs, or logs.
+node shell-agent.mjs connect
+# invitation pasted on stdin; returns a sessionId
+
+node shell-agent.mjs request SESSION_ID
+{"method":"ask","args":{"kind":"command","prompt":"pwd","taskId":"workdir-1","expectedSequence":12}}
+
+node shell-agent.mjs request SESSION_ID
+{"method":"wait_task","args":{"taskId":"workdir-1","afterRevision":1,"timeoutMs":15000}}
+
+node shell-agent.mjs request SESSION_ID
+{"method":"collect_task","args":{"taskId":"workdir-1"}}
+The sessionId returned by connect is the handle the agent reuses for every request, for status, and for stop. Each handoff carries a stable task ID chosen by the agent. Retrying the same ID and prompt retrieves the existing task instead of running the command twice, and a new handoff is blocked until the previous answer is collected or explicitly cancelled. Collecting an answer leaves the connection open, which is the whole point of the v0.15.0 change.
+The part of the protocol most worth reading is how a task becomes done. The statuses are waiting, needs_attention, completed, collected, and cancelled, and the protocol states plainly that "a sent receipt, output, silence, a redraw, and a background-job acknowledgement do not establish completion." Every final result records where its completion claim came from, and there are only three sources.
+This matters when the wrapped process is an interactive TUI such as Claude Code rather than a shell that emits markers. A generic TUI cannot be treated as a structured conversation API. If the agent collects before there is completion evidence, the protocol stores an unconfirmed, truncated screen excerpt and leaves the task pending. If the shell emits no OSC 133 markers at all, the Commands view stays empty rather than guessing where a command ended.
+A control grant is not a free keyboard. Every remote write requires an expectedSequence taken from a recent read, so a stale view of the screen is rejected. Local keystrokes are tracked before PTY echo, including IME composition, and any intervening human input removes the agent's ownership of a draft. Pressing Enter requires an agent-owned draft. No draft is ever cleared automatically. For an application whose composer state cannot be read from terminal cells, the agent must either wait, or the owner can inspect the input themselves and click the button labelled "I've checked: input is empty", which is a one-time acknowledgement that further input or output invalidates. Colors and cursor placement are display evidence, never permission to type.
+Two boundaries hold regardless of the grant. Revoking access is immediate and does not send Ctrl-C or stop the shared process, so a runaway agent loses the terminal but your job keeps running. And the invitation grants terminal access only. Files stay unavailable unless the host separately started the share with the --files or --files-root flag, the opt-in described in the security model, and even then access is rooted, traversal and symlink escapes are rejected, and printing a filename never reads the disk.
+The access rules underneath are unchanged. The share URL and password together are a bearer credential: anyone holding both can open the terminal, and unless the share was started with the read-only flag, can type with the permissions of the wrapped process. The agent invitation sits on top of that share, is scoped to this one live terminal, and cannot be replayed once used. The hosted grant lasts up to four hours.
+The relay sees encrypted payloads, connection metadata, frame sizes, and timing, as it does for every shell.online session. Connector state on the agent's disk contains only its protected local IPC credentials, never terminal contents, answers, or the private invitation. Task history is bounded to the last 32 records with answers capped at 32,768 characters, and expired task IDs stay reserved across snapshots so an old retry cannot resubmit a command.
+Refstream is an unstable alpha and is labelled that way in the renderer menu. It is not binary-compatible with the xterm.js addon ecosystem, and some private xterm behaviours are not implemented yet. If a TUI renders or handles input incorrectly, switch the renderer back to xterm.js. The process, relay connection, and permissions do not change when you switch.
The contrast with a hosted sandbox is not about which is safer in the abstract. It is about who holds the process. In a managed harness the provider runs the container and you get files, packages, and artifacts back. With a browser link to a terminal that never leaves your machine, you hold the process, the agent gets a scoped and revocable seat at it, and every claim of "done" carries a label saying how it was established. Both shell.online and the Refstream renderer are MIT licensed, so the grant model described here can be checked against the code rather than taken on trust. If you have not used the basic share before, the introduction to the live terminal link covers the one-command flow this builds on.
+One command shares the process. One invitation lets an agent in. One click revokes it, and the job keeps running.
Try shell.online