AGENTS.md@packages/coding-agent/src/core/extensions/builtin/terminal · git:20260824.0549249 · 2026-08-24 · sha256 bac35c6b37738a39

AGENTS.md@packages/coding-agent/src/core/extensions/builtin/terminal git:20260824.0549249A

Immutable. This exact content is served forever at /api/v1/blob/bac35c6b37738a39.

# builtin/terminal

Builtin extension #18. Replaces one-shot bash with a **PTY-backed persistent session** model: `bash` plus companion tools `bash_output`, `bash_input`, `bash_resize`, `kill_bash`, and `monitor`. Registered after `bash-timeout` (so the resolved default timeout reaches PTY bash) and after `anthropic-bash` (so a native Anthropic bash tool makes terminal step aside).

## FILES

```
terminal/
├── index.ts             # Barrel: extension + settings + tool-name constants + shared key/regex helpers
├── extension.ts         # Registration entry — registers all six tools, wires lifecycle + reload bundles
├── manager.ts           # TerminalManager: session map ownership
├── runtime-session.ts   # TerminalRuntimeSession: one live PTY
├── session-bundle.ts    # TerminalSessionBundle: reload parking/claiming across extension generations
├── monitor-registry.ts  # MonitorRegistry: registered watches over session output
├── monitor-notify.ts    # Event → notification delivery (283 LOC, largest non-tool file)
├── monitor-status*.ts   # Footer status text + 1s unref'd ticker
├── notify.ts            # TerminalNotifier
├── output-format.ts     # Output shaping/sanitization
├── settings.ts          # loadTerminalSettings / resolveTerminalSettings
├── shared.ts            # Defaults: 120x40, 10000 scrollback, 32 sessions, 1,000,000 output chars
├── prompt.ts            # Tool prompt guidance
└── tools/               # bash.ts (420 LOC), bash-output/input/resize, kill-bash, monitor,
                         # spawn, render, context, foreground-detach/window, sleep-wait
```

## WHERE TO LOOK

| Task | File |
|---|---|
| Change PTY spawn/exec behavior | `tools/bash.ts`, `tools/spawn.ts` |
| Change auto-detach window (~60s foreground) | `tools/foreground-detach.ts`, `tools/foreground-window.ts` |
| Add/change a monitor condition | `tools/monitor.ts` + `monitor-registry.ts` |
| Change how monitor wakes the session | `monitor-notify.ts` |
| Change footer terminal status | `monitor-status.ts`, `monitor-status-ticker.ts` |
| Change defaults (size, scrollback, caps) | `shared.ts` |
| Survive an extension reload | `session-bundle.ts` |

## CONVENTIONS

- **Monitor is the wait mechanism.** Observable state changes are delivered as events that wake the session; `bash_output` is for peeking, not waiting.
- **Companion tools stay active together** with `bash` and are synchronized with extension lifecycle and session-reload bundles — a companion tool without a live PTY is a bug.
- Tool output is capped at 2,000 lines / 50 KiB and sanitized before it reaches the model; monitor status refreshes on a 1-second unref'd interval.
- TypeBox schemas are **flat root objects, never root unions** (`tools/monitor.ts` is the reference) — several provider conversions rebuild schemas from top-level `properties` and a root `anyOf` arrives empty.
- Environment overrides are injected for tests rather than mutating `process.env`.
- Cross-extension seam: `monitor-state-event.ts` (parent dir) carries `TerminalMonitorStateEvent`, consumed by `goal/`.

## ANTI-PATTERNS

- Using `tmux` for long-running/interactive work through terminal `bash` — the PTY session is the mechanism.
- Polling with `sleep`, foreground wait loops, or repeated `bash_output` while waiting on observable state.
- Letting a companion tool dangle without a live PTY `bash`, or letting an output observer interfere with session ingest.
- Assuming the injected `timeout` kills a background session — it never does; use `kill_bash`.

## NOTES

- `changes.md` records `wait_for`, `block`, and `timeout` as **deprecated ghost schema parameters**: still accepted for compatibility, but not the current control model. Do not build new behavior on them.
- The default bash timeout itself is owned by `builtin/bash-timeout/`, not here; terminal consumes the resolved value.