feat(subagents): add kitty as a supported terminal backend#31
Open
tcrasset wants to merge 5 commits intoHazAT:mainfrom
Open
feat(subagents): add kitty as a supported terminal backend#31tcrasset wants to merge 5 commits intoHazAT:mainfrom
tcrasset wants to merge 5 commits intoHazAT:mainfrom
Conversation
Uses kitten @ remote control CLI to implement all surface operations. Each subagent gets a new kitty tab (kitten @ launch --type=tab). Surface identifier is the kitty window ID returned by launch. Operations: - createSurface: kitten @ launch --type=tab, returns numeric window ID - sendCommand: kitten @ send-text --match id:<id> - readScreen: kitten @ get-text --match id:<id> - closeSurface: kitten @ close-window --match id:<id> --no-response - renameTab: kitten @ set-tab-title (no match = current tab) - renameWorkspace: no-op (kitty has no session concept) Detection: KITTY_WINDOW_ID env var + kitten command present. Auto-detection order places kitty last so tmux-inside-kitty prefers tmux. Force with PI_SUBAGENT_MUX=kitty. Requires allow_remote_control yes in kitty.conf + full kitty restart. Emits an actionable error when kitten reports remote control is disabled.
Root cause ---------- kitten @ communicates via APC escape sequences sent through the terminal pty. When invoked from the main pi Node.js process, kitty's response bytes race with pi's own stdin reader (which runs in raw terminal mode). For kitten @ get-text the response can be several kilobytes of screen content; those bytes bleed into pi's input buffer and appear as if typed by the user. Fix: readScreen / readScreenAsync ---------------------------------- If KITTY_LISTEN_ON is set (requires listen_on in kitty.conf) use kitten @ --to <socket> get-text which communicates via a Unix socket and never touches the pty. If no socket is available, return "" so pollForExit falls through to the file-based detection mechanisms (see sentinel-file commit) instead of reading the terminal screen. Fix: pollForExit sentinel file ------------------------------- The sentinel file check previously always returned exitCode 0. It now reads the file contents (the exit code written by the launch script) so crash exits are reported correctly.
pollForExit has three detection mechanisms in priority order: 1. .exit sidecar (written by subagent_done / caller_ping) 2. sentinelFile (was Claude Code only) 3. screen polling (reads terminal for __SUBAGENT_DONE_N__) For pi subagents the sentinelFile was never set, leaving screen polling as the only crash-detection mechanism. On kitty this triggers kitten @ get-text every second, causing APC-over-pty interference. The launch script now captures the exit code before any subsequent command can overwrite $?: pi ...; _PI_EXIT=$?; echo $_PI_EXIT > /path/to/script.done; echo '__SUBAGENT_DONE_'$_PI_EXIT'__' The .done path is stored on RunningSubagent.sentinelFile and passed to pollForExit. The sentinel-file check (already in pollForExit and now reading the exit code from the file contents) fires on the next 1-second tick after any crash — no screen read required.
Agents with auto-exit: true shut down by calling ctx.shutdown() from the agent_end event handler — bypassing the subagent_done tool, which is the only previous writer of the .exit sidecar file. So for every auto-exit agent (scout, worker, reviewer) the sidecar was never written and pollForExit always fell through to the slow screen-polling path (readScreenAsync every second). On kitty this triggers kitten @ get-text on every poll tick, causing the APC response bytes to race with pi's stdin reader and appear as typed characters in the main session's input field. The agent_end handler now writes the .exit sidecar before calling ctx.shutdown(). pollForExit's fast path (file check, no screen read) fires on the next tick, so a normal auto-exit run produces zero kitten @ get-text calls.
subagent_resume only propagated PI_CODING_AGENT_DIR to the resumed process. Three other env vars were missing: PI_SUBAGENT_AUTO_EXIT=1 Without this the auto-exit listener in subagent-done.ts is never attached, so the resumed session does not shut down automatically when the agent finishes, and never writes the .exit sidecar. PI_SUBAGENT_SESSION=<path> Without this subagent_done and caller_ping cannot find the session file path to write the .exit sidecar — their writeFileSync call is guarded by an existence check on this env var. PI_SUBAGENT_NAME=<name> Without this the running-subagents widget shows no agent label for the resumed session.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uses kitten @ remote control CLI to implement all surface operations.
Each subagent gets a new kitty tab (kitten @ launch --type=tab).
Surface identifier is the kitty window ID returned by launch.
Operations:
Detection: KITTY_WINDOW_ID env var + kitten command present.
Auto-detection order places kitty last so tmux-inside-kitty prefers tmux.
Force with PI_SUBAGENT_MUX=kitty.
Requires allow_remote_control yes in kitty.conf + full kitty restart.
Emits an actionable error when kitten reports remote control is disabled.
Screencast.From.2026-04-22.17-03-48.mp4