diff --git a/internal/instance/manager.go b/internal/instance/manager.go index bd579d5..02eb4fd 100644 --- a/internal/instance/manager.go +++ b/internal/instance/manager.go @@ -327,10 +327,38 @@ func (m *Manager) SendInput(id string, input string) error { } m.mu.Lock() in := m.inputs[id] + cmd := m.running[id] m.mu.Unlock() if in == nil { return fmt.Errorf("instance input unavailable: %s", id) } + + // Process control characters (Ctrl+C/Z/\) + // These send signals to the process group and return immediately. + // Any characters after the control character in the same input are discarded. + // This matches real terminal behavior where Ctrl+C interrupts immediately. + // Rationale: once a signal is sent, the process state changes (may exit/suspend), + // and sending additional input is usually not meaningful. + for _, ch := range input { + switch ch { + case 0x03: + if cmd != nil && cmd.Process != nil { + _ = terminatePID(cmd.Process.Pid, syscall.SIGINT) + } + return nil + case 0x1A: + if cmd != nil && cmd.Process != nil { + _ = terminatePID(cmd.Process.Pid, syscall.SIGTSTP) + } + return nil + case 0x1C: + if cmd != nil && cmd.Process != nil { + _ = terminatePID(cmd.Process.Pid, syscall.SIGQUIT) + } + return nil + } + } + if _, err := io.WriteString(in, input); err != nil { return err } diff --git a/internal/redact/redact.go b/internal/redact/redact.go index 4ca9a1d..c529d12 100644 --- a/internal/redact/redact.go +++ b/internal/redact/redact.go @@ -10,11 +10,18 @@ var ( skPattern = regexp.MustCompile(`\bsk-[A-Za-z0-9_-]{16,}\b`) // Generic bearer-like secrets in logs. bearerPattern = regexp.MustCompile(`\bBearer\s+[A-Za-z0-9._-]{16,}\b`) + // Terminal control sequence remnants (without ESC prefix) + // Matches patterns like [<35;55;34M, [535;52;38M, [