mirror of
https://github.com/wirenboard/agent-vm.git
synced 2026-07-09 16:00:54 +00:00
fix(run): route --auto-publish status lines through the raw-mode-safe terminator
The `--auto-publish` "watching" line and per-port event lines (`run.rs`)
are emitted from the launch path — the events from a background task during
the live, raw-mode session — using bare-`\n` `eprintln!`. Same failure mode
as the update banner: under raw mode (ONLCR off) a bare LF doesn't return the
carriage, so the line staircases.
Extract the CRLF-vs-LF decision into a shared `status_line_ending(is_tty)`
helper (the "shared raw-mode-safe writer" the earlier review flagged),
have `update_banner` use it, and switch the three auto-publish messages to
`eprint!("…{nl}")` with `nl = status_line_ending(stderr().is_terminal())`.
The per-event task samples is_terminal() once before its loop — a fd's
TTY-ness is stable for the process.
Add a unit test for `status_line_ending`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
222e86bed6
commit
a565f233a7
1 changed files with 38 additions and 18 deletions
|
|
@ -981,11 +981,17 @@ pub async fn launch(agent: Agent, args: Args) -> Result<i32> {
|
|||
// whether auto-publish is enabled — when disabled the runtime
|
||||
// never emits events, so the subscriber just idles. Cheap.
|
||||
if args.auto_publish {
|
||||
eprintln!("==> auto-publish: watching guest LISTEN sockets via /proc/net/tcp{{,6}}");
|
||||
// These print from a background task during the live (raw-mode)
|
||||
// session, so terminate each line raw-mode-safely; see
|
||||
// `status_line_ending`. is_terminal() is stable for the process, so
|
||||
// the per-event task samples it once before its loop.
|
||||
let nl = status_line_ending(std::io::stderr().is_terminal());
|
||||
eprint!("==> auto-publish: watching guest LISTEN sockets via /proc/net/tcp{{,6}}{nl}");
|
||||
let sb_for_events = sandbox.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut events = sb_for_events.port_events().await;
|
||||
use microsandbox::protocol::network::PortEvent;
|
||||
let nl = status_line_ending(std::io::stderr().is_terminal());
|
||||
while let Some(event) = events.recv().await {
|
||||
match event {
|
||||
PortEvent::Added {
|
||||
|
|
@ -993,8 +999,8 @@ pub async fn launch(agent: Agent, args: Args) -> Result<i32> {
|
|||
host_port,
|
||||
guest_port,
|
||||
} => {
|
||||
eprintln!(
|
||||
"==> auto-publish: guest :{guest_port} → host {host_bind}:{host_port}"
|
||||
eprint!(
|
||||
"==> auto-publish: guest :{guest_port} → host {host_bind}:{host_port}{nl}"
|
||||
);
|
||||
}
|
||||
PortEvent::Removed {
|
||||
|
|
@ -1002,8 +1008,8 @@ pub async fn launch(agent: Agent, args: Args) -> Result<i32> {
|
|||
host_port,
|
||||
guest_port,
|
||||
} => {
|
||||
eprintln!(
|
||||
"==> auto-publish: guest :{guest_port} closed (released {host_bind}:{host_port})"
|
||||
eprint!(
|
||||
"==> auto-publish: guest :{guest_port} closed (released {host_bind}:{host_port}){nl}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1745,7 +1751,13 @@ fn shell_escape(s: &str) -> String {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── update_banner ────────────────────────────────────────────
|
||||
// ── status_line_ending / update_banner ───────────────────────
|
||||
|
||||
#[test]
|
||||
fn status_line_ending_is_crlf_on_tty_and_lf_otherwise() {
|
||||
assert_eq!(status_line_ending(true), "\r\n");
|
||||
assert_eq!(status_line_ending(false), "\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_banner_uses_crlf_on_tty_so_second_line_doesnt_staircase() {
|
||||
|
|
@ -2288,20 +2300,28 @@ async fn notify_if_update_available(image: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Render the two-line "update available" banner with a line terminator
|
||||
/// chosen for where it's being written.
|
||||
/// Line terminator for a status line written to stderr from the launch path,
|
||||
/// given whether stderr is a TTY.
|
||||
///
|
||||
/// This banner runs in a background task (see the spawn on the launch path)
|
||||
/// and can land *after* the interactive session has switched the terminal
|
||||
/// to raw mode (crossterm::enable_raw_mode in the exec path). In raw mode
|
||||
/// the terminal's ONLCR output post-processing is off, so a bare `\n` is a
|
||||
/// line feed with no carriage return: the cursor drops a row but holds its
|
||||
/// column, staircasing the second line off the end of the first. Emitting an
|
||||
/// explicit CRLF on a TTY keeps both lines flush-left whether or not raw mode
|
||||
/// is active. For redirected stderr (`is_tty == false`) we stay with a plain
|
||||
/// `\n` so logs and pipes don't pick up stray carriage returns.
|
||||
/// Several launch-path messages are emitted from background tasks — the
|
||||
/// update-available banner and the `--auto-publish` port events — that can run
|
||||
/// *after* the interactive session has switched the terminal to raw mode
|
||||
/// (`crossterm::enable_raw_mode` in the exec path). In raw mode the terminal's
|
||||
/// ONLCR output post-processing is off, so a bare `\n` is a line feed with no
|
||||
/// carriage return: the cursor drops a row but holds its column, staircasing
|
||||
/// the next line off the end of the previous one. A CRLF on a TTY renders
|
||||
/// flush-left whether or not raw mode is active. For redirected stderr
|
||||
/// (`is_tty == false`) we keep a plain `\n` so logs and pipes don't pick up
|
||||
/// stray carriage returns. (Raw mode doesn't change a fd's TTY-ness, so the
|
||||
/// choice is stable for the life of the process.)
|
||||
fn status_line_ending(is_tty: bool) -> &'static str {
|
||||
if is_tty { "\r\n" } else { "\n" }
|
||||
}
|
||||
|
||||
/// Render the two-line "update available" banner, each line terminated by
|
||||
/// [`status_line_ending`] so both render flush-left even under raw mode.
|
||||
fn update_banner(cached: &str, remote: &str, is_tty: bool) -> String {
|
||||
let nl = if is_tty { "\r\n" } else { "\n" };
|
||||
let nl = status_line_ending(is_tty);
|
||||
format!(
|
||||
"==> A newer image is available in the registry (cached {cached}, registry {remote}){nl}\
|
||||
==> Run `agent-vm pull` to fetch it. Continuing with the cached image.{nl}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue