From e28d39aab697b38dbd871eabdf5147095f3f9a57 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 13 Feb 2026 15:03:27 +0100 Subject: [PATCH] fix: improve kiro-cli state detection and add Refresh method - IsBusy now checks /proc/PID/stat for process state (R/D = busy) when no child processes exist, fixing false idle detection - Add Refresh() to Session interface to re-detect state on demand --- internal/backend/backend.go | 3 +++ internal/backend/tmux/session.go | 20 ++++++++++++++++++++ internal/backends/kirocli.go | 22 +++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/internal/backend/backend.go b/internal/backend/backend.go index e6b2d11..80874be 100644 --- a/internal/backend/backend.go +++ b/internal/backend/backend.go @@ -31,6 +31,9 @@ type Session interface { // State returns current state ("starting", "idle", "running", "error: ...", "exited") State() string + // Refresh retries startup if in error state, or refreshes state detection + Refresh(ctx context.Context) error + // Send sends a prompt and returns the response // Blocks until response is complete Send(ctx context.Context, prompt string) (string, error) diff --git a/internal/backend/tmux/session.go b/internal/backend/tmux/session.go index 83bbe68..28b7221 100644 --- a/internal/backend/tmux/session.go +++ b/internal/backend/tmux/session.go @@ -475,3 +475,23 @@ func (s *Session) Close() error { s.state = "exited" return nil } + +// Refresh re-detects state based on process activity +func (s *Session) Refresh(ctx context.Context) error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.state == "exited" { + return nil + } + + // Check if process is busy + busy := s.stateInspector != nil && s.stateInspector.IsBusy(s.pid) + if busy { + s.state = "running" + } else { + s.state = "idle" + } + debug.Log("[session %s] refresh: state=%s", s.id, s.state) + return nil +} diff --git a/internal/backends/kirocli.go b/internal/backends/kirocli.go index 5b15022..8a7ce91 100644 --- a/internal/backends/kirocli.go +++ b/internal/backends/kirocli.go @@ -46,7 +46,27 @@ func (i *kiroStateInspector) IsBusy(panePID int) bool { } // Check if kiro-cli-chat has any children (tool executions) cmd := exec.Command("pgrep", "-P", fmt.Sprintf("%d", chatPID)) - return cmd.Run() == nil + if cmd.Run() == nil { + return true // has children = busy + } + // No children - check if process is actively running (not sleeping waiting for input) + return isProcessActive(chatPID) +} + +// isProcessActive checks if a process is doing work (not blocked waiting for input) +func isProcessActive(pid int) bool { + data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) + if err != nil { + return false + } + fields := strings.Fields(string(data)) + if len(fields) < 3 { + return false + } + state := fields[2] + // R = running, D = disk sleep (I/O), both indicate active work + // S = interruptible sleep (waiting for input) = idle + return state == "R" || state == "D" } // kiroCommands implements command handling for kiro-cli