Files
kandev/apps/backend/internal/agent/agents/tui_agent_test.go
T
2026-09-24 14:45:00 +01:00

109 lines
4.4 KiB
Go

package agents
import (
"context"
"testing"
"time"
"github.com/kandev/kandev/internal/agent/mcpconfig"
)
// Custom TUI agents wrap arbitrary CLIs, commonly Ink-based TUIs such as Claude
// Code. Ink absorbs a trailing "\r" into a pasted burst instead of dispatching
// Enter, so the submit byte needs its own delayed write. Bracketed-paste
// framing stays on: without it a body larger than one terminal read reaches the
// TUI as a burst it silently drops whole reads from. Custom TUI agents must
// inherit both defaults or programmatic PTY prompts (peer messaging,
// queued-message drain, workflow auto-start) arrive truncated or unsubmitted.
func TestNewTUIAgentDefaultsToInkSafePassthrough(t *testing.T) {
a := NewTUIAgent(TUIAgentConfig{
AgentID: "custom-tui",
AgentName: "custom-tui",
Command: "claude",
Desc: "custom",
})
pt := a.PassthroughConfig()
if pt.DisableBracketedPaste {
t.Error("DisableBracketedPaste = true, want false (framing is what carries a body past one terminal read)")
}
if pt.SubmitDelay != 150*time.Millisecond {
t.Errorf("SubmitDelay = %v, want 150ms (split submit byte into a discrete keystroke so Ink does not absorb it into a paste burst)", pt.SubmitDelay)
}
if got := EffectiveSubmitSequence(pt.SubmitSequence); got != "\r" {
t.Errorf("effective submit sequence = %q, want %q", got, "\r")
}
}
// TestTUIAgentDerivesSupportsMCPFromStrategy pins the mechanism that makes the
// flag survive. Discovery writes IsInstalled's SupportsMCP back over the
// agents.supports_mcp column on every sweep (controller.upsertAgent), so a flag
// set independently at creation is reverted within a boot. Deriving it here
// makes that write keep the flag correct instead of clobbering it.
func TestTUIAgentDerivesSupportsMCPFromStrategy(t *testing.T) {
withStrategy := NewTUIAgent(TUIAgentConfig{
AgentID: "with", AgentName: "with", Command: "echo",
MCPStrategy: mcpconfig.ClaudeStrategy{},
})
result, err := withStrategy.IsInstalled(context.Background())
if err != nil {
t.Fatalf("IsInstalled: %v", err)
}
if !result.SupportsMCP {
t.Error("SupportsMCP = false for an agent with a strategy, want true")
}
without := NewTUIAgent(TUIAgentConfig{AgentID: "without", AgentName: "without", Command: "echo"})
result, err = without.IsInstalled(context.Background())
if err != nil {
t.Fatalf("IsInstalled: %v", err)
}
if result.SupportsMCP {
t.Error("SupportsMCP = true for an agent with no strategy, want false")
}
}
// TestTUIAgentCarriesStrategyIntoPassthroughConfig pins the field the launch
// path actually reads: applyPassthroughMCP returns immediately when
// PassthroughConfig.MCPStrategy is nil, so a strategy that does not land here
// injects nothing no matter what else is configured.
func TestTUIAgentCarriesStrategyIntoPassthroughConfig(t *testing.T) {
a := NewTUIAgent(TUIAgentConfig{
AgentID: "x", AgentName: "x", Command: "echo",
MCPStrategy: mcpconfig.CodexStrategy{},
})
if a.PassthroughConfig().MCPStrategy == nil {
t.Fatal("PassthroughConfig.MCPStrategy = nil, want the configured strategy")
}
}
// TestInteractiveMCPToolsSplitFromPassthroughOnly pins the distinction between
// two questions that used to share one predicate. IsPassthroughOnly governs
// execution mode (it seeds the default profile as CLI passthrough), so it must
// stay true for every TUI agent. Interactive-tool registration is separate: a
// TUI agent wrapping a real MCP-capable CLI should get ask_user_question, which
// it never could while both answers came from the same type assertion.
func TestInteractiveMCPToolsSplitFromPassthroughOnly(t *testing.T) {
withStrategy := NewTUIAgent(TUIAgentConfig{
AgentID: "with", AgentName: "with", Command: "echo",
MCPStrategy: mcpconfig.ClaudeStrategy{},
})
without := NewTUIAgent(TUIAgentConfig{AgentID: "without", AgentName: "without", Command: "echo"})
// Both are passthrough-only: neither has an ACP protocol mode. Weakening
// this would seed them as ACP agents and break them outright.
if !IsPassthroughOnly(withStrategy) {
t.Error("IsPassthroughOnly = false for a TUI agent with a strategy, want true")
}
if !IsPassthroughOnly(without) {
t.Error("IsPassthroughOnly = false for a plain TUI agent, want true")
}
if !SupportsInteractiveMCPTools(withStrategy) {
t.Error("SupportsInteractiveMCPTools = false for a TUI agent with a strategy, want true")
}
if SupportsInteractiveMCPTools(without) {
t.Error("SupportsInteractiveMCPTools = true for a plain TUI agent, want false")
}
}