Commit b04642061 for llama.cpp

commit b04642061d183dff8504127dfbee1c1a8352682c
Author: Pascal <admin@serveurperso.com>
Date:   Wed Sep 30 16:24:46 2026 +0200

    cli: exit on stdin EOF and drop the console wide Ctrl+C broadcast (#29722)

    * cli: exit on stdin EOF and drop the console wide Ctrl+C broadcast

    On Windows the simple input reader sends CTRL_C_EVENT to every process
    attached to the console when stdin reaches EOF, killing unrelated
    processes such as a supervising agent. The CLI only stopped on EOF
    because of that self inflicted SIGINT; on POSIX, and with the advanced
    reader, it spins forever printing prompts.

    Drop the broadcast so both platforms just return an empty read, and
    treat an empty read as EOF in the chat loop and the model selection,
    since a submitted line always ends with a newline.

    * cli: keep the newline of a trailing "/" and stop mtmd-cli on EOF

    A lone "/" came back as an empty read and was taken for EOF, and
    mtmd-cli only stopped on EOF through the removed broadcast.

diff --git a/common/console.cpp b/common/console.cpp
index 9020eb513..54fea813e 100644
--- a/common/console.cpp
+++ b/common/console.cpp
@@ -1019,6 +1019,7 @@ namespace console {
                     line.clear();
                     pop_cursor();
                 }
+                line += '\n';
                 has_more = false;
             }
         } else {
@@ -1050,7 +1051,6 @@ namespace console {
         if (!std::getline(std::wcin, wline)) {
             // Input stream is bad or EOF received
             line.clear();
-            GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
             return false;
         }

@@ -1065,7 +1065,7 @@ namespace console {
         if (!line.empty()) {
             char last = line.back();
             if (last == '/') { // Always return control on '/' symbol
-                line.pop_back();
+                line.back() = '\n';
                 return false;
             }
             if (last == '\\') { // '\\' changes the default action
diff --git a/tools/cli/cli-context.cpp b/tools/cli/cli-context.cpp
index aa4eb7679..d8787c13d 100644
--- a/tools/cli/cli-context.cpp
+++ b/tools/cli/cli-context.cpp
@@ -255,7 +255,7 @@ bool cli_context::list_and_ask_models() {
         ui::user_turn user_turn;
         selection = user_turn.read_input(false, "Select model by number: ");
         if (selection.empty()) {
-            continue;
+            return false;
         }
         try {
             size_t idx = std::stoul(selection);
@@ -502,8 +502,13 @@ int cli_context::run() {
             break;
         }

+        // a submitted line always ends with a newline, an empty read is EOF
+        if (buffer.empty()) {
+            break;
+        }
+
         // remove trailing newline
-        if (!buffer.empty() && buffer.back() == '\n') {
+        if (buffer.back() == '\n') {
             buffer.pop_back();
         }

diff --git a/tools/mtmd/mtmd-cli.cpp b/tools/mtmd/mtmd-cli.cpp
index 6fe058fd8..4156602e4 100644
--- a/tools/mtmd/mtmd-cli.cpp
+++ b/tools/mtmd/mtmd-cli.cpp
@@ -529,6 +529,10 @@ int main(int argc, char ** argv) {
             console::readline(line, false);
             if (g_is_interrupted) break;
             console::set_display(DISPLAY_TYPE_RESET);
+            // a submitted line always ends with a newline, an empty read is EOF
+            if (line.empty()) {
+                break;
+            }
             line = string_strip(line);
             if (line.empty()) {
                 continue;