diff --git a/x/agent/approval.go b/x/agent/approval.go index 434986904..992493949 100644 --- a/x/agent/approval.go +++ b/x/agent/approval.go @@ -7,8 +7,6 @@ import ( "path/filepath" "strings" "sync" - "syscall" - "time" "golang.org/x/term" ) @@ -178,28 +176,6 @@ func FormatDeniedResult(command string, pattern string) string { return fmt.Sprintf("Command blocked: this command matches a dangerous pattern (%s) and cannot be executed. If this command is necessary, please ask the user to run it manually.", pattern) } -// flushStdin drains any buffered input from stdin. -// This prevents leftover input from previous operations from affecting the selector. -func flushStdin(fd int) { - // Set non-blocking mode - if err := syscall.SetNonblock(fd, true); err != nil { - return - } - defer syscall.SetNonblock(fd, false) - - // Give a tiny window for any pending input to arrive - time.Sleep(5 * time.Millisecond) - - // Drain any buffered input - buf := make([]byte, 256) - for { - n, err := syscall.Read(fd, buf) - if n <= 0 || err != nil { - break - } - } -} - // extractBashPrefix extracts a prefix pattern from a bash command. // For commands like "cat tools/tools_test.go | head -200", returns "cat:tools/" // For commands without path args, returns empty string. @@ -597,10 +573,11 @@ func runSelector(fd int, oldState *term.State, toolDisplay string, isWarning boo } return selected, "", nil - // Backspace - delete from reason + // Backspace - delete from reason (UTF-8 safe) case ch == 127 || ch == 8: if len(state.denyReason) > 0 { - state.denyReason = state.denyReason[:len(state.denyReason)-1] + runes := []rune(state.denyReason) + state.denyReason = string(runes[:len(runes)-1]) updateReasonInput(state) } diff --git a/x/agent/approval_unix.go b/x/agent/approval_unix.go new file mode 100644 index 000000000..a96d80166 --- /dev/null +++ b/x/agent/approval_unix.go @@ -0,0 +1,27 @@ +//go:build !windows + +package agent + +import ( + "syscall" + "time" +) + +// flushStdin drains any buffered input from stdin. +// This prevents leftover input from previous operations from affecting the selector. +func flushStdin(fd int) { + if err := syscall.SetNonblock(fd, true); err != nil { + return + } + defer syscall.SetNonblock(fd, false) + + time.Sleep(5 * time.Millisecond) + + buf := make([]byte, 256) + for { + n, err := syscall.Read(fd, buf) + if n <= 0 || err != nil { + break + } + } +} diff --git a/x/agent/approval_windows.go b/x/agent/approval_windows.go new file mode 100644 index 000000000..4bf0b9aa6 --- /dev/null +++ b/x/agent/approval_windows.go @@ -0,0 +1,15 @@ +//go:build windows + +package agent + +import ( + "os" + + "golang.org/x/sys/windows" +) + +// flushStdin clears any buffered console input on Windows. +func flushStdin(_ int) { + handle := windows.Handle(os.Stdin.Fd()) + _ = windows.FlushConsoleInputBuffer(handle) +}