From c802ba5803de1a53749bb5c4ecbc0159fceb385f Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 8 Feb 2026 16:31:40 +0200 Subject: refactor tmuxedit to Agent interface with cursor/claude/config implementations Replace monolithic AgentConfig struct with an Agent interface backed by baseAgent defaults and separate implementations for cursor (box-drawing extraction, bulk backspace clearing) and claude (section-scoped extraction with continuation lines, vim clearing). Simple agents (amp, aider) and user-defined agents use configAgent with baseAgent defaults. Co-Authored-By: Claude Opus 4.6 --- internal/tmuxedit/config_agent.go | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 internal/tmuxedit/config_agent.go (limited to 'internal/tmuxedit/config_agent.go') diff --git a/internal/tmuxedit/config_agent.go b/internal/tmuxedit/config_agent.go new file mode 100644 index 0000000..2773025 --- /dev/null +++ b/internal/tmuxedit/config_agent.go @@ -0,0 +1,134 @@ +package tmuxedit + +import ( + "strings" + + "codeberg.org/snonux/hexai/internal/appconfig" +) + +// configAgent uses baseAgent defaults for all operations. It serves +// user-defined agents from TOML config and simple built-ins (amp, aider) +// that don't need specialized extraction or clearing logic. +type configAgent struct{ baseAgent } + +// builtinAgents returns the default set of agent implementations. Order +// matters: agents with distinctive UI elements (box-drawing, etc.) are +// checked first to avoid false positives from model names like "Claude +// 4.5 Sonnet" appearing in other agents' panes. +func builtinAgents() []Agent { + return []Agent{ + newCursorAgent(), + newClaudeAgent(), + &configAgent{baseAgent{ + name: "amp", + displayName: "Amp", + detectPattern: `(?i)(amp|sourcegraph)`, + promptPat: `(?m)>\s*(.+)$`, + clearFirst: true, + clearKeys: "C-u", + newlineKeys: "S-Enter", + submitKeys: "Enter", + }}, + &configAgent{baseAgent{ + name: "aider", + displayName: "Aider", + detectPattern: `(?i)aider`, + promptPat: `(?m)>\s*(.+)$`, + clearFirst: true, + clearKeys: "C-u", + newlineKeys: "", + submitKeys: "Enter", + }}, + } +} + +// genericAgent returns a fallback agent with no detection or prompt extraction. +// The user gets a blank editor and text is sent verbatim. +func genericAgent() Agent { + return &configAgent{baseAgent{ + name: "generic", + displayName: "Generic", + newlineKeys: "", + submitKeys: "Enter", + }} +} + +// resolveAgents merges built-in agent defaults with user-provided overrides +// from config. Agents are matched by name (case-insensitive); user config +// wins field-by-field over builtins. The Configurable interface provides +// access to baseAgent fields for merging. +func resolveAgents(cfgAgents []appconfig.TmuxEditAgentCfg) []Agent { + agents := builtinAgents() + for _, ca := range cfgAgents { + merged := false + for i, a := range agents { + if !strings.EqualFold(a.Name(), ca.Name) { + continue + } + if c, ok := a.(Configurable); ok { + mergeAgentConfig(c.Base(), ca) + } + merged = true + _ = i // index not needed; we modify through the pointer + break + } + if !merged { + agents = append(agents, agentFromConfig(ca)) + } + } + return agents +} + +// mergeAgentConfig overrides fields in base with non-zero values from cfg. +// It modifies the baseAgent in place via pointer. +func mergeAgentConfig(base *baseAgent, cfg appconfig.TmuxEditAgentCfg) { + if s := strings.TrimSpace(cfg.DisplayName); s != "" { + base.displayName = s + } + if s := strings.TrimSpace(cfg.DetectPattern); s != "" { + base.detectPattern = s + } + if s := strings.TrimSpace(cfg.SectionPattern); s != "" { + base.sectionPat = s + } + if s := strings.TrimSpace(cfg.PromptPattern); s != "" { + base.promptPat = s + } + if len(cfg.StripPatterns) > 0 { + base.stripPatterns = cfg.StripPatterns + } + if cfg.ClearFirst != nil { + base.clearFirst = *cfg.ClearFirst + } + if s := strings.TrimSpace(cfg.ClearKeys); s != "" { + base.clearKeys = s + } + if s := strings.TrimSpace(cfg.NewlineKeys); s != "" { + base.newlineKeys = s + } + if s := strings.TrimSpace(cfg.SubmitKeys); s != "" { + base.submitKeys = s + } +} + +// agentFromConfig creates a new configAgent from a user config entry. +func agentFromConfig(cfg appconfig.TmuxEditAgentCfg) Agent { + b := baseAgent{ + name: strings.TrimSpace(cfg.Name), + displayName: strings.TrimSpace(cfg.DisplayName), + detectPattern: strings.TrimSpace(cfg.DetectPattern), + sectionPat: strings.TrimSpace(cfg.SectionPattern), + promptPat: strings.TrimSpace(cfg.PromptPattern), + stripPatterns: cfg.StripPatterns, + clearKeys: strings.TrimSpace(cfg.ClearKeys), + newlineKeys: strings.TrimSpace(cfg.NewlineKeys), + submitKeys: strings.TrimSpace(cfg.SubmitKeys), + } + if cfg.ClearFirst != nil { + b.clearFirst = *cfg.ClearFirst + } + if b.displayName == "" { + b.displayName = b.name + } + return &configAgent{b} +} -- cgit v1.2.3