Replace quiescence detection with process tree inspection

Detect idle state by checking if kiro-cli-chat has tool children
rather than relying purely on output quiescence. Fixes premature
idle detection during long-running processes like remote builds.
This commit is contained in:
Levi Neely 2026-02-12 16:01:24 +01:00
parent 4253c52f15
commit fe56df8930
2 changed files with 64 additions and 4 deletions

View File

@ -109,6 +109,44 @@ func getPanePID(target string) (int, error) {
return pid, err
}
// hasToolChildren checks if kiro-cli-chat has any child processes (tool executions)
func hasToolChildren(panePID int) bool {
// Find kiro-cli-chat PID in the process tree: pane -> bash -> kiro-cli -> kiro-cli-chat
chatPID := findKiroChatPID(panePID)
if chatPID == 0 {
return false
}
// Check if kiro-cli-chat has any children
cmd := exec.Command("pgrep", "-P", fmt.Sprintf("%d", chatPID))
return cmd.Run() == nil
}
// findKiroChatPID traverses the process tree to find kiro-cli-chat
func findKiroChatPID(panePID int) int {
// pane (kiro-cli-term) -> bash -> kiro-cli -> kiro-cli-chat
bashPID := findChildByName(panePID, "bash")
if bashPID == 0 {
return 0
}
kiroPID := findChildByName(bashPID, "kiro-cli")
if kiroPID == 0 {
return 0
}
return findChildByName(kiroPID, "kiro-cli-chat")
}
// findChildByName finds a child process with the given name
func findChildByName(parentPID int, name string) int {
cmd := exec.Command("pgrep", "-P", fmt.Sprintf("%d", parentPID), "-x", name)
out, err := cmd.Output()
if err != nil {
return 0
}
var pid int
fmt.Sscanf(strings.TrimSpace(string(out)), "%d", &pid)
return pid
}
// makeNonBlocking sets a file descriptor to non-blocking mode
func makeNonBlocking(fd int) error {
return syscall.SetNonblock(fd, true)

View File

@ -336,7 +336,10 @@ func (s *Session) Send(ctx context.Context, prompt string) (string, error) {
func (s *Session) waitForComplete(ctx context.Context, prompt string) error {
const minContent = 50
const quiescence = 400 * time.Millisecond
const quiescence = 300 * time.Millisecond
const pollInterval = 100 * time.Millisecond
var quiesceStart time.Time
for {
select {
@ -349,10 +352,29 @@ func (s *Session) waitForComplete(ctx context.Context, prompt string) error {
return fmt.Errorf("session closed")
}
s.output.Write(data)
quiesceStart = time.Time{} // Reset quiescence on new data
case <-time.After(quiescence):
if s.output.Len() > minContent {
debug.Log("[session %s] quiescence detected, %d bytes", s.id, s.output.Len())
case <-time.After(pollInterval):
// Check if we have minimum content
if s.output.Len() < minContent {
continue
}
// Check process tree: if kiro-cli-chat has tool children, still running
if hasToolChildren(s.pid) {
quiesceStart = time.Time{} // Reset quiescence
continue
}
// No tool children - start or continue quiescence timer
if quiesceStart.IsZero() {
quiesceStart = time.Now()
continue
}
// Check if quiescence period has passed
if time.Since(quiesceStart) >= quiescence {
debug.Log("[session %s] idle detected (no tool children, %d bytes)", s.id, s.output.Len())
return nil
}
}