@@ -83,6 +83,8 @@ type AIChatPane struct {
8383 codeBlockInfos []dirtracker.CodeBlockInfo // Code blocks with language tags
8484 blockDirMappings []string // Effective dir per code block index
8585 workspaceRoot string // From AppConfig.WorkspaceDir
86+ runningCmd * exec.Cmd // Currently running command process (for kill support)
87+ processKilled bool // Whether the process was killed by user (Ctrl+K)
8688}
8789
8890// AIResponseMsg is sent when AI response chunk is received.
@@ -134,6 +136,9 @@ type AIAvailabilityMsg struct {
134136 Available bool
135137}
136138
139+ // ClearStatusMsg is sent to clear the status message in the app.
140+ type ClearStatusMsg struct {}
141+
137142// LanguageCheckMsg is sent when a language runtime check is needed.
138143type LanguageCheckMsg struct {
139144 FileType string // The file type being checked (e.g., "go", "python")
@@ -582,9 +587,12 @@ func (a *AIChatPane) Update(msg tea.Msg) tea.Cmd {
582587 case AINotificationMsg :
583588 a .DisplayNotification (msg .Content )
584589 case TerminalOutputMsg :
585- a .terminalOutput = append (a .terminalOutput , msg .Line )
586- if a .terminalMode {
587- a .viewModeScroll = len (a .terminalOutput )
590+ // Ignore output if process was killed by user
591+ if ! a .processKilled {
592+ a .terminalOutput = append (a .terminalOutput , msg .Line )
593+ if a .terminalMode {
594+ a .viewModeScroll = len (a .terminalOutput )
595+ }
588596 }
589597 return func () tea.Msg {
590598 return <- msg .Output
@@ -593,12 +601,20 @@ func (a *AIChatPane) Update(msg tea.Msg) tea.Cmd {
593601 a .cmdRunning = false
594602 a .stdinWriter = nil
595603 a .terminalInput = ""
596- a .terminalOutput = append (a .terminalOutput , "" )
597- if msg .Err != nil {
598- a .terminalOutput = append (a .terminalOutput , fmt .Sprintf ("[Process failed: %v]" , msg .Err ))
599- } else {
600- a .terminalOutput = append (a .terminalOutput , fmt .Sprintf ("[Process exited with code %d]" , msg .ExitCode ))
604+
605+ // Only show exit message if process wasn't killed by user
606+ if ! a .processKilled {
607+ a .terminalOutput = append (a .terminalOutput , "" )
608+ if msg .Err != nil {
609+ a .terminalOutput = append (a .terminalOutput , fmt .Sprintf ("[Process failed: %v]" , msg .Err ))
610+ } else {
611+ a .terminalOutput = append (a .terminalOutput , fmt .Sprintf ("[Process exited with code %d]" , msg .ExitCode ))
612+ }
601613 }
614+
615+ // Reset the killed flag for next execution
616+ a .processKilled = false
617+
602618 if a .terminalMode {
603619 a .viewModeScroll = len (a .terminalOutput )
604620 }
@@ -618,6 +634,9 @@ func (a *AIChatPane) Update(msg tea.Msg) tea.Cmd {
618634func (a * AIChatPane ) executeCommand (script string , cwd string ) tea.Cmd {
619635 outChan := make (chan tea.Msg )
620636
637+ // Reset the killed flag for new execution
638+ a .processKilled = false
639+
621640 // Determine effective working directory
622641 effectiveDir := cwd
623642 if effectiveDir == "" {
@@ -669,13 +688,17 @@ func (a *AIChatPane) executeCommand(script string, cwd string) tea.Cmd {
669688 cmd .Dir = effectiveDir
670689 }
671690
691+ // Store the running command so it can be killed with Ctrl+K
692+ a .runningCmd = cmd
693+
672694 stdin , _ := cmd .StdinPipe ()
673695 stdout , _ := cmd .StdoutPipe ()
674696 stderr , _ := cmd .StderrPipe ()
675697
676698 a .stdinWriter = stdin
677699
678700 if err := cmd .Start (); err != nil {
701+ a .runningCmd = nil
679702 outChan <- TerminalDoneMsg {Err : err }
680703 return
681704 }
@@ -718,6 +741,7 @@ func (a *AIChatPane) executeCommand(script string, cwd string) tea.Cmd {
718741 }
719742 }
720743 a .stdinWriter = nil
744+ a .runningCmd = nil
721745 outChan <- TerminalDoneMsg {ExitCode : exitCode , Err : err }
722746 }()
723747
@@ -858,6 +882,38 @@ func (a *AIChatPane) handleKeyPress(msg tea.KeyMsg) tea.Cmd {
858882 // When a command is running in terminal mode, forward input to the process
859883 if a .terminalMode && a .cmdRunning && a .stdinWriter != nil {
860884 switch keyStr {
885+ case "ctrl+k" :
886+ // Kill the running process
887+ if a .runningCmd != nil && a .runningCmd .Process != nil {
888+ // Set flag to ignore further output
889+ a .processKilled = true
890+ // Close stdin first
891+ if a .stdinWriter != nil {
892+ a .stdinWriter .Close ()
893+ }
894+
895+ // Kill the process tree (important on Windows to kill child processes)
896+ pid := a .runningCmd .Process .Pid
897+ if runtime .GOOS == "windows" {
898+ // On Windows, use taskkill to kill the entire process tree
899+ killCmd := exec .Command ("taskkill" , "/F" , "/T" , "/PID" , fmt .Sprintf ("%d" , pid ))
900+ killCmd .Run ()
901+ } else {
902+ // On Unix, just kill the process
903+ a .runningCmd .Process .Kill ()
904+ }
905+
906+ // Mark as not running
907+ a .cmdRunning = false
908+ a .terminalOutput = append (a .terminalOutput , "" , "[Process killed by user (Ctrl+K)]" )
909+ a .viewModeScroll = len (a .terminalOutput )
910+
911+ // Return a command to clear the status message in the app
912+ return func () tea.Msg {
913+ return ClearStatusMsg {}
914+ }
915+ }
916+ return nil
861917 case "esc" :
862918 // Esc always exits — close stdin and let the process finish
863919 a .stdinWriter .Close ()
@@ -1626,7 +1682,7 @@ func (a *AIChatPane) renderViewMode() string {
16261682 MarginBottom (1 ).
16271683 Padding (0 , 1 ).
16281684 Width (a .width - 4 ).
1629- Render (" ⚙ [Enter] Send Input | [Esc] Close Stdin | [↑↓/PgUp/PgDn] Scroll " )
1685+ Render (" ⚙ [Enter] Send Input | [Ctrl+K] Kill Process | [ Esc] Close Stdin | [↑↓/PgUp/PgDn] Scroll " )
16301686 } else {
16311687 instructions = lipgloss .NewStyle ().
16321688 Foreground (lipgloss .Color ("15" )).
@@ -1918,7 +1974,7 @@ func (a *AIChatPane) GetLastAssistantResponse() string {
19181974
19191975// RunScript enters terminal mode and executes the given command, streaming
19201976// output into the AI chat pane. Returns a tea.Cmd to start the execution.
1921- func (a * AIChatPane ) RunScript (command string , label string ) tea.Cmd {
1977+ func (a * AIChatPane ) RunScript (command string , label string , workingDir string ) tea.Cmd {
19221978 if a .cmdRunning {
19231979 return nil
19241980 }
@@ -1937,7 +1993,7 @@ func (a *AIChatPane) RunScript(command string, label string) tea.Cmd {
19371993 }
19381994 a .viewModeScroll = 0
19391995
1940- return a .executeCommand (command , "" )
1996+ return a .executeCommand (command , workingDir )
19411997}
19421998
19431999// EnterConfigMode enters the configuration editor mode.
0 commit comments