Skip to content

terminal: run a program in a pane for another plugin (terminal.run) - #18

Merged
foxnne merged 1 commit into
mainfrom
terminal-run
Oct 19, 2026
Merged

foxnne merged 1 commit into
mainfrom
terminal-run

Conversation

@foxnne

@foxnne foxnne commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Registers the internal command terminal.run that fizzyedit/chat#18 defined (chat's src/auth.zig, TerminalRun / Exited), so the chat's terminal sign-in works: Claude Code's ACP adapter offers only a terminal method (claude /login). The plugins meet only at the host's commands; neither imports the other.

What it does

  • terminal.run .{ .title, .argv, .env, .cwd, .done, .tag } opens a bottom-panel pane of its own (ghostty.run.<n>, titled title) and reveals it (host.revealSurface). The command fails, so the chat says it could not open a terminal, when the arguments don't parse, argv is empty, or the app has no place to show the pane.
  • The program runs with the login shell's PATH (core.shell_env, resolved off the GUI thread: a Dock-started fizzy has only the system's) under the request's env, in cwd (empty: the project folder, else home), TERM=xterm-256color.
  • A waiter thread hears the program's own exit (waitid with WNOWAIT, then a reap under the lock that guards signalling, so a hangup never reaches a recycled pid), not the terminal closing: a helper it left holding the tty can't hide the exit. It gives the reader up to 250 ms to drain, and the next frame calls done with .{ .tag, .code }: 128 + signal when a signal ended it, 127 when it could not start.
  • The pane has a bar with the title, the exit code once there is one, and Close. Closing a pane that is still running hangs up on the program's process group (then kills it after 200 ms) and tells done 129. A plugin unload does the same for every run whose caller is still loaded. The pane stays after the exit until it is closed.
  • Pty.open takes a Command (argv + env) or null for the shell. On unix the child's argv, environment and resolved path are all built before forkpty, so the child only chdirs and execves (the shell had a setenv after fork before). The shell is now reaped when it exits. On Windows a command's program is found on PATH with PATHEXT. A batch file (npx.cmd) runs through cmd.exe /d /c, and an argument cmd would read as syntax (% & | < > ^ ( ) ! ") is refused rather than escaped. The environment block always carries TERM, and a waiter thread reports the exit, since ConPTY keeps the output pipe open after it. Windows has only been cross-compiled (x86_64 and aarch64).

The chat already checks for the command at runtime: initialize sends auth.terminal: host.hasCommand("terminal.run"), and the sign-in buttons offer a terminal method only while that holds. Nothing has to change in chat.

No SDK seam was missing. registerSurface / revealSurface / unregisterSurface at runtime, plus an internal runWith command, cover it.

Verified

  • zig build test (new step, run by CI on each OS): parses the request as the chat writes it and ignores unknown fields; writes Exited back as the chat reads it; puts the login PATH under the request's env. The unix PTY test runs sh from PATH with an argument, an env var and a cwd and gets them back with exit code 3, and checks that a signal gives 128+15 and a missing program 127. It also checks that an exit is heard while a backgrounded sleep holds the tty, and that closing a program which traps HUP returns.
  • zig build for native, x86_64-windows-gnu and x86_64-linux-gnu.
  • End to end on macOS, with one --quiet fizzy (main + this plugin + chat main) in its own profile and a stand-in ACP agent that is logged out and offers a terminal method (--login, LOGIN_MODE=interactive). The chat got auth.terminal: true; session/new was refused, and the sign-in button opened a "Sign in: Log in in a terminal" pane running agent.py --login with the env and TERM. Typing yes into the pane (a demo tape) made it exit 0. The pane showed exited with code 0, the chat started the agent again, session/new succeeded, and the chat said "Connected." Close removed the pane. In a second run (after the review fixes), Close on a pane still waiting for input killed the login. The chat said "ended without signing in (exit 129)" and offered the button again, and the next sign-in went through.

🤖 Generated with Claude Code

Registers the internal command `terminal.run` that fizzyedit/chat#18 defined, so the chat's
terminal sign-in (Claude Code's ACP adapter offers only `claude /login`) works. The program runs in
a bottom-panel pane of its own with the request's argv, env and cwd; on exit the pane keeps its
output with the exit code and a Close button, and the caller's `done` command gets `.{ tag, code }`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne marked this pull request as ready for review October 19, 2026 20:22
@foxnne
foxnne merged commit d65c618 into main Oct 19, 2026
6 checks passed
@foxnne foxnne mentioned this pull request Oct 19, 2026
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