diff --git a/acme-q b/acme-q index 10aeb4c..bfffcc9 100755 Binary files a/acme-q and b/acme-q differ diff --git a/internal/backends/kirocli.go b/internal/backends/kirocli.go index b66d97d..24189ca 100644 --- a/internal/backends/kirocli.go +++ b/internal/backends/kirocli.go @@ -3,7 +3,7 @@ package backends import ( "acme-q/internal/backend" - "acme-q/internal/backend/pty" + "acme-q/internal/backend/tmux" "context" "fmt" "log" @@ -16,7 +16,7 @@ import ( // NewKiroCLI creates a kiro-cli backend func NewKiroCLI() backend.Backend { - return pty.New(pty.Config{ + return tmux.New(tmux.Config{ Name: "kiro-cli", Command: []string{"kiro-cli", "chat", "--trust-all-tools"}, Environment: map[string]string{ @@ -24,7 +24,7 @@ func NewKiroCLI() backend.Backend { "NO_COLOR": "1", "COLUMNS": "999", }, - PTYSize: pty.PTYSize{ + TmuxSize: tmux.TmuxSize{ Rows: 40, Cols: 120, }, @@ -89,24 +89,88 @@ func (c *kiroCleaner) cleanResponse(raw string) string { lines := strings.Split(clean, "\n") var result []string + foundUserPrompt := false inResponse := false for _, line := range lines { trimmed := strings.TrimSpace(line) - // Check for response marker "> " (not in prompt brackets) - if idx := strings.Index(line, "> "); idx >= 0 { - if !strings.Contains(line, "[") || !strings.Contains(line, "]") { - inResponse = true - text := strings.TrimSpace(line[idx+2:]) - if text != "" { - result = append(result, text) - } - continue - } + // Look for user prompt (starts with "!>") + if !foundUserPrompt && strings.HasPrefix(trimmed, "!>") { + foundUserPrompt = true + continue } - // Skip noise + // Skip echoed user input (the line right after !> that doesn't have markers) + if foundUserPrompt && !inResponse && trimmed != "" && + !strings.Contains(trimmed, "Thinking...") && + !strings.Contains(trimmed, "I will run") && + !strings.Contains(trimmed, "using tool:") && + !strings.Contains(trimmed, "Purpose:") && + !strings.Contains(trimmed, ">") { + continue + } + + // Stop at next prompt (starts with "!>") - but only after we've started collecting + if foundUserPrompt && inResponse && strings.HasPrefix(trimmed, "!>") { + break + } + + // After user prompt, start collecting non-noise content + if foundUserPrompt && trimmed != "" { + // Check if line contains actual content (even if it has noise) + // Important markers: "I will run", "using tool:", "Purpose:", ">", or actual content after spinners + hasContent := false + text := trimmed + + // If line contains "Thinking..." followed by content, extract the content + if strings.Contains(line, "Thinking...") { + idx := strings.Index(line, "Thinking...") + afterThinking := strings.TrimSpace(line[idx+11:]) // "Thinking..." is 11 chars + if afterThinking != "" { + text = afterThinking + hasContent = true + } + } + + // Check for important markers + if !hasContent && (strings.Contains(trimmed, "I will run") || + strings.Contains(trimmed, "using tool:") || + strings.Contains(trimmed, "Purpose:") || + strings.Contains(trimmed, ">")) { + hasContent = true + } + + // If no content markers and it's noise, skip + if !hasContent && c.isNoise(line) { + continue + } + + // If we got here, it's not pure noise + if !hasContent { + text = trimmed + } + + // Start collecting + if !inResponse { + inResponse = true + } + + // If line has ">", strip it (for assistant response lines) + if strings.Contains(text, ">") { + idx := strings.Index(text, ">") + if idx == 0 || (idx > 0 && text[idx-1] != '!') { + text = strings.TrimSpace(text[idx+1:]) + } + } + + if text != "" { + result = append(result, text) + } + continue + } + + // Before user prompt, skip noise and look for hooks if c.isNoise(line) { continue } @@ -116,10 +180,6 @@ func (c *kiroCleaner) cleanResponse(raw string) string { result = append(result, trimmed) continue } - - if inResponse && trimmed != "" { - result = append(result, line) - } } return strings.Join(result, "\n") @@ -169,6 +229,7 @@ func (c *kiroCleaner) isNoise(line string) bool { // Check for noise patterns noisePatterns := []string{ "Thinking...", + "▸ Credits:", "▸ Time:", } diff --git a/internal/p9/server.go b/internal/p9/server.go index ae239cc..f91cde3 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -3,7 +3,7 @@ package p9 import ( "acme-q/internal/backend" - "acme-q/internal/backend/pty" + "acme-q/internal/backend/tmux" "acme-q/internal/backends" "acme-q/internal/session" "context" @@ -374,9 +374,9 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { if err != nil { return errFcall(fc, "invalid winid") } - // Set WinID if it's a PTY session - if ptySess, ok := sess.(*pty.Session); ok { - ptySess.SetWinID(id) + // Set WinID if it's a tmux session + if tmuxSess, ok := sess.(*tmux.Session); ok { + tmuxSess.SetWinID(id) } return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))} } @@ -498,11 +498,11 @@ func (s *Server) getSessionFile(sess backend.Session, idx int) string { switch idx { case fileOut: - // Output from last command - only available for PTY sessions - if ptySess, ok := sess.(*pty.Session); ok { + // Output from last command - only available for tmux sessions + if tmuxSess, ok := sess.(*tmux.Session); ok { // We don't have a direct Output() method anymore // This would need to be stored separately if needed - _ = ptySess + _ = tmuxSess } return "" case fileErr: diff --git a/main.go b/main.go index d2c9fc9..06b32c9 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "acme-q/internal/backend" - "acme-q/internal/backend/pty" + "acme-q/internal/backend/tmux" "acme-q/internal/backends" "acme-q/internal/p9" "acme-q/internal/session" @@ -270,9 +270,9 @@ func openChatWindow(sess backend.Session) (*acme.Win, error) { w.Fprintf("body", "# Session %s\n# cwd: %s\n\nUSER:\n", sess.ID(), meta.Cwd) w.Ctl("clean") - // Set window ID on session (if it's a PTY session) - if ptySess, ok := sess.(*pty.Session); ok { - ptySess.SetWinID(w.ID()) + // Set window ID on session (if it's a tmux session) + if tmuxSess, ok := sess.(*tmux.Session); ok { + tmuxSess.SetWinID(w.ID()) } go handleChatWindow(w, sess)