Terminal (PTY)

A real shell — not a fake REPL — running in your workspace, with full ANSI, multi-tab persistence, and Ctrl+C that actually delivers SIGINT.

What it is

The integrated terminal is a server-side PTY (pseudo-terminal). You type, your bytes get fed to a real bash. Programs see a terminal. top works. vim works. python -i works.

Default-on as of build 2026-04-25

Earlier builds shipped a JavaScript fake-shell as default and required opting into the real PTY. Since ticket 3.2-G, the real PTY is the default.

StateWhat runs
localStorage["kf.term.pty"] unsetReal PTY (default)
localStorage["kf.term.pty"] = "1"Real PTY (forced)
localStorage["kf.term.pty"] = "0"Legacy fake shell
?ptyterm=0 URL paramLegacy fake shell (per-tab)
?ptyterm=1 URL paramReal PTY (per-tab)

To check which is running, in the browser console:

KFTermPTY.flag
// → { key: "kf.term.pty", on: true }

Multi-tab + persistence

  • The + button on the terminal panel header opens a new tab.
  • Each tab is a fresh PTY session with its own working directory.
  • Reload the page → both tabs re-attach to the same PIDs. Your cd is preserved. Background processes keep running.
  • Close a tab with the × button — the underlying PTY is killed.

Session ids are persisted to localStorage["kf.term.pty.sids"].

Agent take-over

When the agent runs a command on your behalf, you'll see an "agent typing" indicator on the active tab. The agent writes through the same PTY as you — you see the output stream identically.

Font size

localStorage.setItem("kf.term.fontSize", "15");
location.reload();

Range: 9-28. Default 13.

What it can't do

  • Doesn't run as root. All PTYs run as the workspace's www-data-equivalent user.
  • Idle timeout. Sessions with no I/O for > the configured TTL are reaped. Long-running daemons survive; idle shells don't.
  • No shared clipboard with your OS by default — use the browser's right-click → paste, or Ctrl+Shift+V (depends on your terminal emulator behaviour).

Going back to the legacy shell

The fake JavaScript shell is still there as a fallback. To switch:

localStorage.setItem("kf.term.pty", "0");
location.reload();

To switch back:

localStorage.removeItem("kf.term.pty");
location.reload();