omp packages

pi-smart-terminal0.5.0

Persistent PTY terminal sessions for pi: overrides the built-in bash tool with a stateful shell, adds terminal_* tools, a live session overlay (/term) and footer status. Powered by smart-terminal-mcp.

GitHub Actions Published 8 days ago MIT ~432 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-smart-terminal

A real, persistent shell for pi — the agent's bash tool runs in a live PTY terminal session instead of a throwaway shell, powered by smart-terminal-mcp's core.

Why

pi's built-in bash tool spawns a fresh shell for every command. Each call runs in a vacuum:

bash: cd packages/api && npm test          # 'cd' is lost instantly — so the agent
bash: cd packages/api && npm run build     # must chain && cd ../.. into everything
bash: export API_KEY=...                   # environment gone by the next call
bash: npm run dev                          # background server: output unreachable,
                                           # process dead or orphaned after the call
bash: python                               # REPLs, npm-init prompts, ssh, docker
                                           # login — anything interactive just hangs

With pi-smart-terminal, bash runs in one persistent PTY session — same shell, same terminal, alive for the whole conversation:

bash: cd packages/api     # the session is now IN packages/api
bash: npm test            # just works — no chaining
bash: export API_KEY=...  # still set on the next call
bash: npm run dev         # keeps running; new output readable at any time
                          #   (terminal_read / terminal_wait / terminal_watch)
/term                     # YOU watch the agent's shell live, as it types

In short:

  • State that sticks — cwd, environment variables and background processes survive between calls. The agent stops wasting tokens on cd chains and absolute paths.
  • Interactive programs work — REPLs, installers with prompts, ssh, test-watchers: a real TTY means they behave like they do in your own terminal, and the agent can answer prompts with terminal_write / terminal_send_key.
  • Long-running processes become usable — start a dev server once, then read its output incrementally, wait for a "listening on" line, or watch for errors — event-driven, instead of re-dumping logs into the conversation.
  • Codemode can drive it — every tool returns typed structuredContent (with an outputSchema), so pi's codemode scripts and nested ctx.executeTool() callers orchestrate sessions programmatically (run → parse → retry loops) without parsing JSON text.
  • Session state survives compaction — the live session list (ids, cwd, busy) is injected into the model context on every request, so the model never forgets its sessionIds or their drifted cwd — even after compaction.
  • You can actually watch it — /term is a live viewer of the agent's shell. See what the model is doing in real time, scroll its full history, while the footer shows busy state and cwd at a glance (auto mode stays silent while idle).
  • Nothing leaks — all sessions are killed (entire process group) when pi exits; no orphaned dev servers after a session.

The terminal_* tool family adds the controls a persistent shell needs (terminal_read, terminal_wait, terminal_watch, terminal_retry, terminal_diff, …) — payload-identical to the smart-terminal-mcp tools, so agent habits transfer 1:1 from Claude Code, Cursor & co.

How it integrates

  • bash override — built-in bash transparently executes in the persistent PTY session (pi's rendering, truncation and timeouts preserved). Falls back to a one-shot shell when the session is busy with a background command.
  • terminal tools — 15 terminal_* tools matching the MCP server; extras load on demand via terminal_tools, the built-in tool_search, or a defaultTools pin.
  • compact tool rendering — every terminal_* call renders as a one-line summary with a status badge (exit code, matched pattern, session count…); on pi ≥ 1.0.1 this uses pi.registerToolRenderer(), so calls to deferred/unregistered tools — including terminal_* calls in resumed sessions and HTML exports — render compactly too.
  • /term — live session viewer (Tab / Shift+Tab switches sessions, structured header, three-zone status bar); footer — auto shows busy/exited states only.
  • Shared shell — opt-in: your ! commands run in the agent's session too.
  • Lifecycle — all PTYs killed (process group) on shutdown; if node-pty fails to load, pi starts normally without this extension.

Codemode, tool_search and error semantics

The tools are first-class citizens of pi's codemode ecosystem (pi ≥ 0.99):

  • Namespace smart-terminal — codemode scripts find the tools with searchTools() and read usage guidance via describeNamespace("smart-terminal").
  • Structured results — every tool declares an outputSchema and returns structuredContent; the model still receives the JSON text you know.
  • Failure with data — terminal_run (failed success criteria), terminal_wait (pattern never appeared) and terminal_watch (timeout without a match) return isError: true results: the model sees the failure, scripts still get the structured payload. terminal_exec keeps exit codes as data (MCP semantics).
  • Annotations — observers (terminal_read/list/wait/watch/get_history/run_paged) carry readOnlyHint, terminal_stop / terminal_write_file carry destructiveHint, so permission extensions can gate them accordingly.
  • Deferred extras — the 8 extra tools register with exposure: "deferred": callable by codemode scripts and findable by the built-in tool_searchat any time, but never auto-declared to the model. Activate them with theterminal_toolsloader, or pin e.g."defaultTools": ["+terminal_watch"](a pin survives; pi ≥ 0.99.2 re-enables it via/reload` too).
  • Compact rendering (pi ≥ 1.0.1) — pi.registerToolRenderer() draws every terminal_* call as a one-line summary plus a status badge: exit code and drifted cwd for terminal_exec/terminal_retry, match result for terminal_wait/terminal_watch, session count for terminal_list, colored +/− stats for terminal_diff. Because the resolver works by tool name rather than registration, it also renders calls to deferred tools that were never activated, and terminal_* calls in resumed sessions and HTML exports — even when the tool behind them is not registered (rendering is display-only and independent of the native PTY chain). On older pi the registration is skipped and pi's default rendering applies.

The session-state injection uses pi's context_with_system boundary: one trailing system message per request carrying a terminal_sessions prompt section (the same mechanism as pi's mcp_servers section). It is stateless — never persisted to history — so the transcript stays clean and the provider prefix remains cache-stable.

Install

pi install npm:pi-smart-terminal

Requirements: pi ≥ 0.99.2 (compact terminal_* rendering via registerToolRenderer on pi ≥ 1.0.1), Node ≥ 20 and a C++ toolchain for node-pty (prebuilt binaries cover common platforms; on Windows use npm rebuild node-pty inside the package if needed).

Configuration

~/.pi/agent/smart-terminal.json (all keys optional):

{
	"overrideBash": true,
	"userBash": false,
	"bashTimeoutMs": 600000,
	"footer": "auto",
	"defaultShell": null,
	"allToolsActive": false,
	"sessionStateContext": true
}
Key Default Meaning
overrideBash true Replace built-in bash with the persistent-session backend.
userBash false Route user ! commands through the shared session (opt-in; full-screen tools like !vim are better left native).
bashTimeoutMs 600000 Hard cap when the model passes no timeout.
footer "auto" Footer status line: "auto" (only while a command runs or a session exited), "minimal" (always-on glyph + count, e.g. ⏵ 2), "full" (verbose id/name/cwd line), "off". true/false still work (= full/off).
defaultShell null Force a shell; null = auto-detect (pwsh > powershell > cmd on Windows, $SHELL > bash > sh elsewhere).
allToolsActive false Register all 15 tools with full schemas instead of lazy loading extras.
sessionStateContext true Inject live session state (ids, cwd, busy) into the model context each request — survives compaction.

How the bash override behaves

  • First call spawns the agent session and waits for the shell banner (~1–2.5 s once).
  • cwd persistence is the point: cd carries over between calls. The per-call cwd is only used when (re-)creating a dead session. Watch drift in the footer, or reset with terminal_stop + a fresh bash call.
  • Parallel bash calls / busy session (a background command is running): transparently falls back to a one-shot stateless execution so the call still succeeds.
  • Timeout (timeout:N) and abort (Esc → ctrl+c to the PTY) map onto pi's native error formats; partial output is preserved.
  • Long-running commands (dev servers): use terminal_exec with quietExitMs, or let a bash call time out — the command keeps running in the session; read new output with terminal_read({ since }), wait for patterns with terminal_wait / terminal_watch, and watch it live in /term.

Multi-line payloads (here-strings, heredocs)

The PTY feeds the shell line by line, so multi-line shell constructs pasted in one go get mangled:

  • PowerShell here-strings (@'...'@): PSReadLine can eat the @' opener mid-paste, and every apostrophe in the body then breaks parsing (Unexpected token ...).
  • POSIX heredocs (<<EOF): the completion markers terminal_exec appends land on the closing-delimiter line, so the heredoc never terminates and the session hangs until the timeout.

That's why terminal_exec (and the bash override, which uses the same path) rejects them up front with the workaround instead of hanging the session. Do this instead:

  1. Write the payload to a file: the write tool, or terminal_write_file (resolves relative paths against the session's drifted cwd).
  2. Reference the file: gh pr create --body-file .pr-body.md, command < input.txt, or --body "$(Get-Content file -Raw)".

terminal_run is unaffected: it spawns a one-shot process with no PTY and no markers, so heredocs genuinely work there. And if a command ever times out with the shell stuck at a continuation prompt (>> / > / More?), the result's hint names the prompt and how to recover: send the closing line with terminal_write, or abort with terminal_send_key("ctrl+c").

Commands

  • /term — live overlay of terminal sessions (opens the most recent; Tab / Shift+Tab to switch, ↑↓/PgUp/PgDn scroll, f follow, q close)
  • /terminals — list active sessions

Tool catalog

Active from the start: terminal_start, terminal_exec, terminal_run, terminal_read, terminal_write, terminal_wait, terminal_stop, terminal_list, plus the terminal_tools loader.

Deferred (load on demand): terminal_run_paged, terminal_retry, terminal_diff, terminal_resize, terminal_send_key, terminal_get_history, terminal_write_file, terminal_watch. Codemode scripts can call them as-is; tool_search finds them; terminal_tools or a defaultTools pin activates them for the model.

Payload shapes are identical to the smart-terminal-mcp tools, so agent habits transfer 1:1 between pi and MCP clients.

Relationship to smart-terminal-mcp

This package is the native pi integration of the same core (smart-terminal-mcp): one core, two adapters. The MCP server remains the portable path for Claude Code, Cursor and friends; inside pi, this extension goes deeper than MCP can — tool schemas, dynamic loading, custom UI, lifecycle hooks.

License

MIT