terminal
Read console output, send input, or run commands and wait for quiet, pattern, or exit completion to capture results.
Instructions
Purpose: Interact with a terminal window: read output, send input, or run+wait+read in one call. action='read' / action='send' absorb the formerly-standalone read/send tools (Phase 4).
Details: action='run' is the recommended high-level workflow: send command → wait until quiet/pattern/timeout → read output. The command text is passed as input (the legacy parameter name command is also accepted as a deprecated alias — see issue #245). Returns completion={reason, elapsedMs} first-class plus outputIntegrity:'ok'|'baseline_lost' so callers can detect when scrollback could not be anchored to the pre-send buffer. action='read' reads current text via UIA TextPattern (falls back to OCR); use sinceMarker for incremental diff. action='send' sends a command with focus management.
Prefer: action='run' for command execution + result. For long-running commands (test runners, builds, deploys) use until:{mode:'pattern', pattern:''} — the default quiet mode is tuned for short interactive commands and may complete prematurely on multi-second silent gaps mid-run. Use action='read'/'send' for fine-grained control or when you need to interleave other actions. read/send/run also accept a launch_console paneId (pass the paneId field, NOT windowTitle) to keep targeting a pane after its title changes.
Caveats: Do not screenshot the terminal — action='read' is cheaper and structured. action='run' supports completion reasons: quiet | pattern_matched | exited | timeout | window_closed | window_not_found | send_failed (send rejected on a live window — see warnings for the underlying error code). until:{mode:'exit', shell:'bash'|'powershell'} (issue #386) returns completion.exitCode + reason:'exited' via an echo-immune sentinel that works for multiline input that pattern mode cannot anchor; pass shell explicitly (auto fails as ExitModeShellAmbiguous on WT/conhost/SSH), cmd is unsupported (ExitModeShellUnsupported), open-construct input is rejected (ExitModeUnsafeInput). When outputIntegrity:'baseline_lost' is returned, output is forced to '' and readError.code='BaselineMarkerLost' is set: rerun with until:{mode:'pattern',...} or longer timeoutMs. action='run' may also emit warnings prefixed FileLockCollision: when output reveals an EBUSY/Windows-lock/EAGAIN-EDEADLK file collision (issue #236). Default quietMs=1500 (issue #196); long silences require pattern mode. preferClipboard=true (send default) replaces the clipboard. Hidden-input prompts emit verifyDelivery.unverifiable (reason:'hidden_input_prompt') — use method:'foreground'. action='read' typed errors: TerminalWindowNotFound, TerminalTextPatternUnavailable (force source:'ocr'); stale sinceMarker → hints.terminalMarker.previousMatched:false on ok:true (omit sinceMarker). FG-path Win11 foreground refusal returns code:'ForegroundRestricted' — switch to method:'background' or DTM_BG_AUTO=1. Off-desktop target → WindowOnOtherDesktop, not sent. BG path auto-engages only when the target class is ConsoleWindowClass (conhost: cmd / PowerShell / pwsh) OR env DTM_BG_AUTO=1. Windows Terminal (CASCADIA_HOSTING_WINDOW_CLASS) is EXCLUDED (issue #173): WT runs on WinUI/XAML and silently drops WM_CHAR, so the FG path is default — pass sendOptions:{method:'background'} only if your WT build accepts BG input.
Examples:
terminal({action:'run', windowTitle:'PowerShell', input:'npm test', until:{mode:'pattern', pattern:'Test Files'}}) → recommended for test runners; matches when vitest summary appears
terminal({action:'run', windowTitle:'pwsh', input:'ls'}) → quiet 1500ms wait, returns output (short interactive)
terminal({action:'run', windowTitle:'pwsh', command:'ls'}) → identical to the above; command is a deprecated alias of input (issue #245)
terminal({action:'read', windowTitle:'PowerShell', sinceMarker:'...'}) → incremental diff using the read action
terminal({action:'send', windowTitle:'PowerShell', input:'echo hello'}) → sends text + Enter using the send action
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| input | No | 'send': Text to send (max 10,000 chars). 'run': Command to send (Enter is appended automatically). Either `input` or its deprecated alias `command` is required. | |
| lines | No | Tail N lines (default 50). | |
| until | No | ||
| action | Yes | Action selector — one of: read, send, run. Per-action required fields are enforced at call time (see the tool description); this flat schema lists every action's fields as optional. | |
| method | No | Input routing channel. 'auto' defaults to background (WM_CHAR) when the target is a known terminal class (Windows Terminal / cmd / PowerShell / conhost) so user-side focus changes mid-stream cannot divert keystrokes. DTM_BG_AUTO=1 enables BG globally; 'auto' falls back to foreground for non-terminal targets. 'background' forces WM_CHAR injection (no focus change). 'foreground' forces the current behavior (SetForegroundWindow + clipboard paste). 'foreground_flash' (ADR-013 Option E) is an explicit opt-in 妥協 BG path for Windows Terminal: temporarily steals foreground (~50-80ms), pastes via clipboard, sends Ctrl+V + Enter (when pressEnter=true), restores foreground + clipboard. Single-line + < 5KiB only. `typingLeakRisk: true` in hints. Default 'auto'. | auto |
| paneId | No | 'read': Pane handle from key_locker launch_console — either the decimal hwnd of a classic console, or the 'wt:…' form for a Windows Terminal tab. Targets THIS pane even after its title changes; takes precedence over windowTitle. NOTE: read still resolves the pane's text by title under the hood, so it declines if the pane's current title is no longer unique among windows (a 'wt:…' pane additionally reads only while its tab is the ACTIVE tab of its Windows Terminal window). 'send': Pane handle from key_locker launch_console — either the decimal hwnd of a classic console (bound directly by hwnd, surviving a title change, e.g. after an ssh login the title becomes user@host), or the 'wt:…' form for a Windows Terminal tab (delivered to its Windows Terminal window while that tab is ACTIVE). Takes precedence over windowTitle. 'run': Pane handle from key_locker launch_console — a decimal console hwnd, or the `wt:<pid>:<startMs>` form for a Windows Terminal tab. Targets THIS pane for the whole run+wait+read: the pane title is re-resolved before every read, so a mid-run retitle (e.g. a classic console renaming to `user@host` after an ssh login) is still tracked. Takes precedence over windowTitle. This is the paneId FIELD of launch_console's result — NOT its windowTitle. A Windows Terminal pane is tracked while its tab is the ACTIVE tab (switching away pauses the reads until you switch back). | |
| source | No | 'auto' = UIA TextPattern then OCR fallback; 'uia' = TextPattern only (fail on miss); 'ocr' = OCR only. | auto |
| command | No | [Deprecated alias of `input`] Accepted for callers that mis-remember the parameter name; new code should use `input`. If both are set, `input` wins. | |
| include | No | Optional response-shape opt-in. `['envelope']` returns the self-documenting envelope (`_version` / `data` / `as_of` / `confidence`). `['raw']` forces raw shape (overrides DESKTOP_TOUCH_ENVELOPE=1 server default). Default behaviour is raw shape (compat with existing clients). | |
| pasteKey | No | Paste key combo. 'auto' picks ctrl+shift+v for WSL/bash/mintty/wezterm/alacritty, ctrl+v elsewhere. Only used when preferClipboard=true. | auto |
| settleMs | No | Milliseconds to wait after sending before checking foreground window (default 300). | |
| chunkSize | No | Split long input into chunks of this many characters in background mode to prevent terminal input queue saturation. Default 100. Only applies when method results in background. | |
| stripAnsi | No | Strip ANSI escape sequences (default true). | |
| timeoutMs | No | Hard timeout in ms (default 30s) | |
| focusFirst | No | Focus the terminal before sending (default true). | |
| forceFocus | No | When true, bypass Windows foreground-stealing protection via AttachThreadInput before focusing the terminal window. Default: follows env DESKTOP_TOUCH_FORCE_FOCUS (default false). | |
| pressEnter | No | Press Enter after typing (default true). | |
| trackFocus | No | When true (default), detect if focus was stolen after sending. Reports focusLost in the response. | |
| ocrLanguage | No | BCP-47 language tag for OCR fallback. Auto-detects from system locale when omitted. | |
| readOptions | No | Extra options forwarded to terminal read (lines, source, ocrLanguage, etc.) | |
| sendOptions | No | Extra options forwarded to terminal send (method, chunkSize, etc.) | |
| sinceMarker | No | Marker returned from a previous call. If found in current text, only the diff is returned. | |
| windowTitle | No | 'read' / 'run': Partial title of the terminal window (e.g. 'PowerShell', 'pwsh', 'WindowsTerminal'). Provide windowTitle OR paneId (paneId takes precedence). 'send': Partial title of the terminal window. Provide windowTitle OR paneId (paneId takes precedence). | |
| restoreFocus | No | Restore the previously-focused window after sending (default true). | |
| preferClipboard | No | Paste the input from the clipboard instead of typing it — safe for long text and not run through IME conversion (does not help while an IME composition is already in progress: the paste keystroke is consumed by the IME and nothing arrives, though the call still answers ok; hints.clipboard.imeOpen says when the IME was on at the paste). Your clipboard is replaced for the duration of the call and put back afterwards, but the restore is skipped when another process wrote to the clipboard first, when its contents are too large for an addon-less build to carry back, or when saving it failed; hints.clipboard reports which backend served the paste and whether the restore ran. Default true. |