diff --git a/cmd/Assist/main.go b/cmd/Assist/main.go index d303323..414b328 100644 --- a/cmd/Assist/main.go +++ b/cmd/Assist/main.go @@ -820,7 +820,7 @@ func openPromptWindow(sess *SessionInfo) (*acme.Win, error) { return nil, err } w.Name(name) - w.Write("tag", []byte("Send Compact Clear ")) + w.Write("tag", []byte("Send Compact Clear Resume ")) w.Ctl("clean") // Track window ID client-side @@ -866,6 +866,10 @@ func handlePromptWindow(w *acme.Win, sess *SessionInfo) { if err := controlSession(sess.ID, "clear"); err != nil { fmt.Fprintf(os.Stderr, "Failed to clear: %v\n", err) } + case "Resume": + if err := controlSession(sess.ID, "resume"); err != nil { + fmt.Fprintf(os.Stderr, "Failed to resume: %v\n", err) + } default: w.WriteEvent(e) } diff --git a/internal/backend/tmux/session.go b/internal/backend/tmux/session.go index c84afe7..579359f 100644 --- a/internal/backend/tmux/session.go +++ b/internal/backend/tmux/session.go @@ -42,6 +42,8 @@ type Session struct { model string // Active model override (empty = backend default) modelResolver ModelResolver // Optional: modifies command to include model selection clearHandler ClearHandler // Optional: backend-specific /clear handling + resumeHandler ResumeHandler // Optional: backend-specific resume handling + compactHandler CompactHandler // Optional: backend-specific /compact handling commands backend.CommandHandler startupHandler StartupHandler @@ -511,6 +513,32 @@ func (s *Session) Clear() error { return sendKeys(target, "/clear", "C-m") } +// Resume resumes the most recent conversation using backend-specific handling. +func (s *Session) Resume() error { + s.mu.Lock() + target := s.target() + handler := s.resumeHandler + s.mu.Unlock() + + if handler != nil { + return handler(target) + } + return fmt.Errorf("resume not implemented for this backend") +} + +// Compact sends the /compact command using backend-specific handling. +func (s *Session) Compact() error { + s.mu.Lock() + target := s.target() + handler := s.compactHandler + s.mu.Unlock() + + if handler != nil { + return handler(target) + } + return fmt.Errorf("compact not implemented for this backend") +} + // stopInternal performs the actual stop operation. // Caller must hold s.mu and have set s.transitioning = true. // This method will unlock s.mu while performing stop operations. diff --git a/internal/backend/tmux/tmux.go b/internal/backend/tmux/tmux.go index bc1d03f..c6023d3 100644 --- a/internal/backend/tmux/tmux.go +++ b/internal/backend/tmux/tmux.go @@ -49,6 +49,16 @@ type ModelResolver func(cmd []string, model string) []string // Returns nil on success. type ClearHandler func(target string) error +// ResumeHandler handles the resume command for a specific backend. +// It receives the tmux target and should send the appropriate keys. +// Returns nil on success. +type ResumeHandler func(target string) error + +// CompactHandler handles the /compact command for a specific backend. +// It receives the tmux target and should send the appropriate keys. +// Returns nil on success. +type CompactHandler func(target string) error + // Config holds tmux backend configuration type Config struct { Name string @@ -62,6 +72,8 @@ type Config struct { NsSuffix string // Optional: namespace suffix (e.g., "0" for :0) ModelResolver ModelResolver // Optional: modifies command to include model selection ClearHandler ClearHandler // Optional: backend-specific /clear handling + ResumeHandler ResumeHandler // Optional: backend-specific resume handling + CompactHandler CompactHandler // Optional: backend-specific /compact handling } // Backend implements backend.Backend for tmux-based CLI tools @@ -358,6 +370,8 @@ func (b *Backend) CreateSession(ctx context.Context, opts backend.SessionOptions model: opts.Model, modelResolver: b.cfg.ModelResolver, clearHandler: b.cfg.ClearHandler, + resumeHandler: b.cfg.ResumeHandler, + compactHandler: b.cfg.CompactHandler, pid: pid, state: "starting", createdAt: time.Now(), diff --git a/internal/backends/claude.go b/internal/backends/claude.go index f084874..bba45bb 100644 --- a/internal/backends/claude.go +++ b/internal/backends/claude.go @@ -46,10 +46,22 @@ func newClaudeWithCommand(command []string, nsSuffix string) backend.Backend { Commands: &claudeCommands{}, StartupHandler: &claudeStartupHandler{}, StateInspector: &claudeStateInspector{}, + ClearHandler: claudeClearHandler, + CompactHandler: claudeCompactHandler, NsSuffix: nsSuffix, }) } +// claudeClearHandler sends /clear for claude +func claudeClearHandler(target string) error { + return tmux.SendKeysTo(target, "/clear", "C-m") +} + +// claudeCompactHandler sends /compact for claude +func claudeCompactHandler(target string) error { + return tmux.SendKeysTo(target, "/compact", "C-m") +} + // claudeStateInspector implements StateInspector for claude CLI type claudeStateInspector struct{} diff --git a/internal/backends/kirocli.go b/internal/backends/kirocli.go index 48e369f..ccc1a12 100644 --- a/internal/backends/kirocli.go +++ b/internal/backends/kirocli.go @@ -34,6 +34,8 @@ func NewKiroCLI(nsSuffix string) backend.Backend { StartupTime: 30 * time.Second, StateInspector: &kiroStateInspector{}, ClearHandler: kiroClearHandler, + ResumeHandler: kiroResumeHandler, + CompactHandler: kiroCompactHandler, NsSuffix: nsSuffix, }) } @@ -47,6 +49,20 @@ func kiroClearHandler(target string) error { return tmux.SendKeysTo(target, "y", "C-m") } +// kiroResumeHandler sends /chat resume and confirms selection for kiro-cli +func kiroResumeHandler(target string) error { + if err := tmux.SendKeysTo(target, "/chat resume", "C-m"); err != nil { + return err + } + time.Sleep(250 * time.Millisecond) + return tmux.SendKeysTo(target, "C-m") +} + +// kiroCompactHandler sends /compact for kiro-cli +func kiroCompactHandler(target string) error { + return tmux.SendKeysTo(target, "/compact", "C-m") +} + // kiroStateInspector implements StateInspector for kiro-cli type kiroStateInspector struct{} diff --git a/internal/p9/server.go b/internal/p9/server.go index d54eb8c..90f603f 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -853,7 +853,13 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } case "compact": if tmuxSess, ok := sess.(*tmux.Session); ok { - if err := tmuxSess.SendRaw("/compact"); err != nil { + if err := tmuxSess.Compact(); err != nil { + return errFcall(fc, err.Error()) + } + } + case "resume": + if tmuxSess, ok := sess.(*tmux.Session); ok { + if err := tmuxSess.Resume(); err != nil { return errFcall(fc, err.Error()) } }