fix(repl): skip /quit on EOF when stdin is not a TTY (#724)

When running as a launchd/systemd daemon, stdin is /dev/null.
rustyline reads EOF immediately and the REPL thread was sending
a /quit message, causing the agent to shut down right after
startup — making service mode non-functional on both macOS and Linux.

Fix: check std::io::stdin().is_terminal() before sending /quit on
EOF. In daemon mode (no TTY) the REPL thread exits silently, leaving
other channels (gateway, telegram, …) running as expected.

Fixes #723

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Zaki Manian <[email protected]>
This commit is contained in:
Xing Ji
2026-03-08 20:40:56 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.6 Zaki Manian
parent da2569bb77
commit fe91ba2ab4
+10 -5
View File
@@ -18,7 +18,7 @@
//! - `Esc` - Interrupt current operation
use std::borrow::Cow;
use std::io::{self, Write};
use std::io::{self, IsTerminal, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
@@ -411,10 +411,15 @@ impl Channel for ReplChannel {
}
}
Err(ReadlineError::Eof) => {
// Ctrl+D: send /quit so the agent loop runs graceful shutdown
let msg =
IncomingMessage::new("repl", "default", "/quit").with_timezone(&sys_tz);
let _ = tx.blocking_send(msg);
// Ctrl+D in interactive mode: graceful shutdown.
// In daemon mode (stdin = /dev/null, no TTY), EOF arrives
// immediately — just drop the REPL thread silently so other
// channels (gateway, telegram, …) keep running.
if std::io::stdin().is_terminal() {
let msg = IncomingMessage::new("repl", "default", "/quit")
.with_timezone(&sys_tz);
let _ = tx.blocking_send(msg);
}
break;
}
Err(e) => {