feat: Add Cursor, Auggie, and Sourcegraph AMP agent presets (#247)

* feat: add Cursor Agent as compatible agent for Gas Town

Add AgentCursor preset with ProcessNames field for multi-agent detection:
- AgentCursor preset: cursor-agent -p -f (headless + force mode)
- ProcessNames field on AgentPresetInfo for agent detection
- IsAgentRunning(session, processNames) in tmux package
- GetProcessNames(agentName) helper function

Closes: ga-vwr

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: centralize agent preset list in config.go

Replace hardcoded ["claude", "gemini", "codex"] arrays with calls to
config.ListAgentPresets() to dynamically include all registered agents.

This fixes cursor agent not appearing in `gt config agent list` and
ensures new agent presets are automatically included everywhere.

Also updated doc comments to include "cursor" in example lists.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add comprehensive agent client tests

Add tests for agent detection and command generation:

- TestIsAgentRunning: validates process name detection for all agents
  (claude/node, gemini, codex, cursor-agent)
- TestIsAgentRunning_NonexistentSession: edge case handling
- TestIsClaudeRunning: backwards compatibility wrapper
- TestListAgentPresetsMatchesConstants: ensures ListAgentPresets()
  returns all AgentPreset constants
- TestAgentCommandGeneration: validates full command line generation
  for all supported agents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add Auggie agent, fix Cursor interactive mode

Add Auggie CLI as supported agent:
- Command: auggie
- Args: --allow-indexing
- Supports session resume via --resume flag

Fix Cursor agent configuration:
- Remove -p flag (requires prompt, breaks interactive mode)
- Clear SessionIDEnv (cursor uses --resume with chatId directly)
- Keep -f flag for force/YOLO mode

Updated all test cases for both agents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(agents): add Sourcegraph AMP as agent preset

Add AgentAmp constant and builtinPresets entry for Sourcegraph AMP CLI.

Configuration:
- Command: amp
- Args: --dangerously-allow-all --no-ide
- ResumeStyle: subcommand (amp threads continue <threadId>)
- ProcessNames: amp

Closes: ga-guq

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: lint error in cleanBeadsRuntimeFiles

Change function to not return error (was always nil).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: beads v0.46.0 compatibility and test fixes

- Add custom types config (agent,role,rig,convoy,event) after bd init calls
- Fix tmux_test.go to use variadic IsAgentRunning signature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update agent documentation for new presets

- README.md: Update agent examples to show cursor/auggie, add built-in presets list
- docs/reference.md: Add cursor, auggie, amp to built-in agents list
- CHANGELOG.md: Add entry for new agent presets under [Unreleased]

Addresses PR #247 review feedback.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mike Lady
2026-01-07 20:35:06 -08:00
committed by GitHub
parent 585c204648
commit 92042d679c
12 changed files with 413 additions and 21 deletions

View File

@@ -309,3 +309,119 @@ func TestEnsureSessionFresh_IdempotentOnZombie(t *testing.T) {
t.Error("expected session to exist after multiple EnsureSessionFresh calls")
}
}
func TestIsAgentRunning(t *testing.T) {
if !hasTmux() {
t.Skip("tmux not installed")
}
tm := NewTmux()
sessionName := "gt-test-agent-" + t.Name()
// Clean up any existing session
_ = tm.KillSession(sessionName)
// Create session (will run default shell)
if err := tm.NewSession(sessionName, ""); err != nil {
t.Fatalf("NewSession: %v", err)
}
defer func() { _ = tm.KillSession(sessionName) }()
// Get the current pane command (should be bash/zsh/etc)
cmd, err := tm.GetPaneCommand(sessionName)
if err != nil {
t.Fatalf("GetPaneCommand: %v", err)
}
tests := []struct {
name string
processNames []string
wantRunning bool
}{
{
name: "empty process list",
processNames: []string{},
wantRunning: false,
},
{
name: "matching shell process",
processNames: []string{cmd}, // Current shell
wantRunning: true,
},
{
name: "claude agent (node) - not running",
processNames: []string{"node"},
wantRunning: cmd == "node", // Only true if shell happens to be node
},
{
name: "gemini agent - not running",
processNames: []string{"gemini"},
wantRunning: cmd == "gemini",
},
{
name: "cursor agent - not running",
processNames: []string{"cursor-agent"},
wantRunning: cmd == "cursor-agent",
},
{
name: "multiple process names with match",
processNames: []string{"nonexistent", cmd, "also-nonexistent"},
wantRunning: true,
},
{
name: "multiple process names without match",
processNames: []string{"nonexistent1", "nonexistent2"},
wantRunning: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tm.IsAgentRunning(sessionName, tt.processNames...)
if got != tt.wantRunning {
t.Errorf("IsAgentRunning(%q, %v) = %v, want %v (current cmd: %q)",
sessionName, tt.processNames, got, tt.wantRunning, cmd)
}
})
}
}
func TestIsAgentRunning_NonexistentSession(t *testing.T) {
if !hasTmux() {
t.Skip("tmux not installed")
}
tm := NewTmux()
// IsAgentRunning on nonexistent session should return false, not error
got := tm.IsAgentRunning("nonexistent-session-xyz", "node", "gemini", "cursor-agent")
if got {
t.Error("IsAgentRunning on nonexistent session should return false")
}
}
func TestIsClaudeRunning(t *testing.T) {
if !hasTmux() {
t.Skip("tmux not installed")
}
tm := NewTmux()
sessionName := "gt-test-claude-" + t.Name()
// Clean up any existing session
_ = tm.KillSession(sessionName)
// Create session (will run default shell, not Claude)
if err := tm.NewSession(sessionName, ""); err != nil {
t.Fatalf("NewSession: %v", err)
}
defer func() { _ = tm.KillSession(sessionName) }()
// IsClaudeRunning should be false (shell is running, not node)
cmd, _ := tm.GetPaneCommand(sessionName)
wantRunning := cmd == "node"
if got := tm.IsClaudeRunning(sessionName); got != wantRunning {
t.Errorf("IsClaudeRunning() = %v, want %v (pane cmd: %q)", got, wantRunning, cmd)
}
}