Skip to content

feat(agent-manager): add WezTerm terminal support#128

Merged
codeaholicguy merged 2 commits into
mainfrom
feature-wezterm-support
Jun 30, 2026
Merged

feat(agent-manager): add WezTerm terminal support#128
codeaholicguy merged 2 commits into
mainfrom
feature-wezterm-support

Conversation

@codeaholicguy

@codeaholicguy codeaholicguy commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Adds support for discovering, focusing, and sending input to agents running inside WezTerm, alongside the existing tmux / iTerm2 / Terminal.app flows.

  • TerminalFocusManager: new WEZTERM terminal type. Discovers the owning WezTerm pane from wezterm cli list --format json by matching the agent PID's TTY → pane id; focuses via wezterm cli focus-pane --pane-id. Probed after tmux, before the macOS AppleScript probes, so non-macOS hosts short-circuit cleanly (tmux-inside-WezTerm still resolves to tmux).
  • TtyWriter: sends text via wezterm cli send-text --pane-id over the child stdin (text, then Enter, 150 ms apart) — the same two-step convention used for the other emulators so a bracketed-paste-aware TUI still sees Enter as a submit.
  • CLI-only integration (no AppleScript) → cross-platform (macOS/Linux/Windows). Missing or non-running wezterm degrades like the tmux probe (null / false), so non-WezTerm users are unaffected.
  • No CLI changes: agent open, agent send, and channel bridges pick up WezTerm automatically through the existing findTerminal / focusTerminal / TtyWriter.send abstract API.

Out of scope (deliberate): spawning new agents into WezTerm (that remains TmuxManager's responsibility).

@codeaholicguy codeaholicguy force-pushed the feature-wezterm-support branch from b86156c to 38ffcda Compare June 30, 2026 02:12
@codeaholicguy

Copy link
Copy Markdown
Owner Author

Updated per review feedback. New HEAD: `38ffcda` (force-pushed, single amended commit).

Changes addressing the three review notes:

  1. tty_name field — WezTerm's `cli list --format json` exposes the TTY as `tty_name`, not `tty`. Fixed `WeztermPaneEntry` and the match in `findWeztermPane` to read `pane.tty_name`. Tests/fixtures updated.

  2. Focus command — `focusWeztermPane` now uses `wezterm cli activate-pane --pane-id ` instead of `focus-pane`. Tests updated.

  3. `agent open --debug` — added a `--debug` option to the `open` command. `TerminalFocusManager` now accepts an optional `debug` logger; when `--debug` is set, it's wired to the existing `ai-devkit:terminal` debug logger (`createLogger('terminal')` + `enableDebug()`, same convention as `agent start`/`channel`). It emits one line per discovery/focus step — pid/tty, each emulator probe's match/no-match (tmux → wezterm → iTerm2 → Terminal.app → UNKNOWN), and the focus result — so the matching/focus decision path is inspectable.

Validation (fresh):

  • `agent-manager` + `cli` `tsc --noEmit` clean; `eslint` 0 errors.
  • vitest: agent-manager 442, cli 843, monorepo 1079 total via the pre-commit hook (`nx run-many -t lint test`).
  • New WezTerm + debug branches covered (TtyWriter 100%; new `findTerminal`/`focusTerminal` debug paths + `tty_name`/ `activate-pane` all hit). Pre-existing uncovered iTerm2/Terminal.app focus methods left untouched to avoid unrelated refactors.
  • `npx ai-devkit@latest lint --feature wezterm-support` passes.

Not merging.

@codeaholicguy codeaholicguy force-pushed the feature-wezterm-support branch from 38ffcda to 1e3fc77 Compare June 30, 2026 02:28
@codeaholicguy

Copy link
Copy Markdown
Owner Author

Updated per the WezTerm send-behavior feedback. New HEAD: `1e3fc77` (force-pushed, single amended commit).

`TtyWriter.sendViaWezterm` now makes two explicit CLI calls (no stdin):

  1. Text — `wezterm cli send-text --pane-id <pane_id> `, with the message passed as a positional argv element via `execFile` (no shell). Because `execFile` spawns the process directly, the bytes are delivered verbatim regardless of shell metacharacters — there is no injection surface (same approach as tmux's `send-keys -l`).
  2. Enter — `wezterm cli send-text --pane-id <pane_id> --no-paste `, where the carriage-return byte is `0x0d` (shell `$'\x0d'`), passed as a discrete argv element. `--no-paste` delivers the CR literally rather than wrapped in paste brackets. (150 ms pacing between the two calls, matching the other senders.)

The user message is never built into a shell command or a shell string.

Code: removed the previous stdin-based `ExecFileInputOptions` workaround entirely (net simplification); added a `CARRIAGE_RETURN = '\x0d'` constant.

Tests added/updated (`TtyWriter.test.ts`):

  • two-call contract: message as last argv element of call 1, then `--no-paste` + CR as call 2, no options/stdin arg;
  • Enter byte is exactly char code `0x0d` (not `0x0a`);
  • shell-injection safety: a hostile message (`$(rm -rf /)`, backticks, `;`, newlines) is passed verbatim as one argv element.

Validation (fresh): `agent-manager` + `cli` `tsc --noEmit` clean; `eslint` 0 errors; pre-commit hook `nx run-many -t lint test` green — agent-manager 444, cli 843. Docs (design/implementation/testing) updated to describe the two-call, argv, CR-byte send. Not merging.

Discover, focus, and type into the WezTerm pane that hosts a running
agent, alongside the existing tmux / iTerm2 / Terminal.app flows.

- TerminalFocusManager: add WEZTERM type; discover the owning pane from
  `wezterm cli list --format json` by matching the agent TTY against the
  JSON entry's `tty_name` field; focus via `wezterm cli activate-pane
  --pane-id`. Tried after tmux and before the macOS-only AppleScript
  probes, so non-macOS hosts short-circuit.
- TtyWriter: send via two explicit `wezterm cli send-text --pane-id` calls
  150ms apart — (1) the message as a positional argv element, then (2)
  Enter as a single carriage return byte (0x0d) with --no-paste. Both are
  discrete argv elements via execFile (no shell), so bytes are delivered
  verbatim regardless of shell metacharacters (injection-safe). The
  equivalent shell command for Enter is:
    wezterm cli send-text --pane-id <id> --no-paste $'\x0d'
  (ANSI-C dollar-single-quoted CR; execFile passes the actual CR byte).
- TerminalFocusManager accepts an optional debug logger; `agent open
  --debug` wires it to the ai-devkit:terminal logger so users can inspect
  the matching/focus decision path.
- Control is CLI-only (no AppleScript), so it works cross-platform.
  Missing/failed wezterm degrades like the tmux probe (null/false).

No other CLI changes: agent send and channel bridges pick up WezTerm
through the existing abstract findTerminal/focusTerminal/TtyWriter.send
API. Spawning agents into WezTerm remains out of scope (TmuxManager).

Validation: agent-manager + cli tsc/eslint clean; vitest 444 (agent-manager)
and 843 (cli) / monorepo pass; WezTerm send/focus/debug branches covered
incl. argv-injection-safety and CR-byte (0x0d) assertions.
@codeaholicguy codeaholicguy force-pushed the feature-wezterm-support branch from 1e3fc77 to f8e8b65 Compare June 30, 2026 02:40
@codeaholicguy

Copy link
Copy Markdown
Owner Author

Notation corrected per review. New HEAD: `f8e8b65` (force-pushed, single amended commit).

Fix: wherever the shell form of the Enter command is written, it now uses dollar-single-quoted CR — `$'\x0d'` with the leading `$` — i.e.

```
wezterm cli send-text --pane-id <pane_id> --no-paste $'\x0d'
```

Implementation is unchanged and still correct: `execFile` passes the actual CR byte as an argv element (JS `'\x0d'` = char `0x0d`), as the review confirmed it should. The distinction is now explicit everywhere:

  • argv value (implementation/test): the CR byte, JS `'\x0d'` — labelled "JS char / argv element" in comments.
  • shell command (docs/comments): `$'\x0d'` (ANSI-C quoting) — bare `'\x0d'` would be passed by a shell as the four literal characters `\ x 0 d`.

Corrected in: `design`, `implementation`, `testing` docs, and the `TtyWriter.ts` + `TtyWriter.test.ts` comments. A grep confirms every shell-command occurrence uses `$'\x0d'`; remaining bare `'\x0d'` are only JS-source argv values (the constant `CARRIAGE_RETURN = '\x0d'`, the argv assertion, and prose explicitly labelled "JS char").

(I considered adding a unit test that asserts the shell-form string, but it would only check a constant defined in the test itself — a tautology. The behavioral test already pins the real contract: the Enter argv element is exactly char code `0x0d`, which is what `$'\x0d'` denotes.)

Validation (fresh): pre-commit hook `nx run-many -t lint test` green — agent-manager 444, cli 843; `tsc --noEmit` clean; `eslint` 0 errors; `lint --feature wezterm-support` passes. Not merging.

@codeaholicguy codeaholicguy merged commit 7fa1ff7 into main Jun 30, 2026
7 checks passed
@codeaholicguy codeaholicguy deleted the feature-wezterm-support branch June 30, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant