Repository navigation
feat(chat): shell mode with ! in the conversation view, running the command locally and letting the agent respond (#79) - #80
Merged
Conversation
…ommand locally and letting the agent respond A message starting with ! was sent to the agent as plain text. Claude Code's shell mode exists only in its terminal UI, and the stream-json protocol the conversation view speaks has no such mode, so a line like "! az login --tenant <id>" just sat there as a prompt. The view now runs the command itself: through the user's login shell in the agent's working directory with the agent's environment, output streaming live into its own row with a Cancel button, no timeout because browser logins take minutes, at most one command per session, output capped per stream. When the command ends, command and output go to the agent in the terminal's own format (<bash-input>, <bash-stdout>, <bash-stderr>) as one message, because two consecutive user messages over stream-json produce two turns; the agent then responds as in the terminal. The tagged message is recognized on every path (live, echo, history replay) and rendered as the command row, never as a bubble. Public shares do not get the command, like pty_spawn; paired clients get it with the same authorization as chat_send. The shell resolution of the terminal and the agent environment of the engine are extracted into shared functions so the two cannot drift; process trees are killed through one host helper. Closes vlinx-io#79 Co-Authored-By: Claude Fable 5.1 <[email protected]>
mguttmann
force-pushed
the
feat/chat-bash-mode
branch
from
September 15, 2026 14:46
55fe647 to
cfd8708
Compare
Author
|
Rebased onto v0.2.2 (7874081). One conflict, adjacent additions in src/ipc/chat.ts (your chatAutoContinueCancel next to chatRunShell and chatCancelShell); both kept. cargo test 1064 passed, build and lint green; pnpm test green apart from two known timeouts in src/ipc/transfer.test.ts under load, which pass 11/11 twice in isolation and are untouched by this branch. |
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.
Closes #79
What happens today
A message that starts with
!is sent to the agent verbatim. Claude Code's shell mode exists only in its terminal UI; over the stream-json protocol the conversation view speaks,! az login --tenant <id>is plain text, so nothing runs and nothing comes back. Details and the probe are in #79.What changes
!commandruns locally. The composer routes a message starting with!to a newchat_run_shellcore function instead ofchat_send. The command runs through the user's login shell (the same resolution the terminal view uses, extracted intoresolve_shell), in the agent's working directory, with the environment the agent process gets (extracted intoagent_environment, so the two cannot drift). The whole text after!is passed to the shell as one argument; VelaTerm never splits or re-quotes it.shellrow (command line, output, running indicator, Cancel, exit code or Cancelled). There is no fixed timeout, because commands such asaz loginwait minutes for a browser login; the user cancels instead. Cancel kills the whole process group through one host helper. Output is capped at 200 KiB per stream, keeping the tail, with a visible truncation note. At most one shell command runs per session at a time.<bash-input>…</bash-input>and<bash-stdout>…</bash-stdout><bash-stderr>…</bash-stderr>(exit code and cancellation noted inside stderr). The terminal sends these as two consecutive messages; over stream-json two consecutive user messages produce two assistant turns (verified with a probe), so the view sends one. If no agent process is alive, it is started first, the way a normal message does it.chat_run_shellandchat_cancel_shellarecommand_corefunctions wired asdispatch.rsarms (the desktop reaches dispatch throughdesktop_call, so there is no separate Tauri adapter, matching the existing chat commands). Paired clients get them with the same guard aschat_send; public share visitors are denied both, likepty_spawn(test added). Both names are listed in the diagnostics allowlists.chat.shell.*keys in en and de, the other nine locales carry the English text with the repo's TODO marker. The manual gains a short section on shell mode in the conversation view.Decisions worth a look
Exit code Non its own line is this PR's convention./dev/null, so a tool that prompts on stdin sees EOF and ends instead of hanging, matching the terminal's shell mode.Known limits, left open on purpose (from the QA and Security reviews)
!starts the agent first when none is alive, the way a normal message does. If the agent cannot start (not installed, sign-in pending), the command does not run either and the composer shows the start error.cmd.exesessions. Rust's argument quoting does not match cmd.exe's rules for quotes inside the command; PowerShell (the default) and Git Bash are unaffected. Untested here (no Windows).zsh -l -creads.zprofile, not.zshrc; a tool whose PATH is set only in.zshrcwill not resolve in shell mode although it does in the terminal.Verification
cargo test1064 passed, 0 failed, 15 ignored; the shell module's own tests cover the tag round trip including output that contains the tags, the tail cap,echo,exit 3, and cancelling asleepwith a process-group check. The headlessvela-serverbuild (--no-default-features) compiles.pnpm test119 files / 1030 tests,pnpm build(type check across the eleven locales) andpnpm lintgreen. New tests cover composer routing (!xnever reacheschat_send, bare!and images show hints and keep the draft, a refusal keeps the draft), the shell row (running with Cancel, exit code, cancelled), the frontend parser, and the WebSocket dispatch parity for both call origins.! lsin a Claude conversation ran locally in the project directory, the row showed the output, and the agent received it as the tagged message and responded to it.! az loginresolvedazthrough the login shell, opened the browser, waited without a timeout, and Cancel ended the command; the agent received the output with theCommand cancelled by the userline.Note on linking: this PR comes from a fork without write access, so GitHub does not add it to the issue's Development sidebar on its own;
Closes #79here and in the commit closes the issue on merge.🤖 Generated with Claude Code